В этой главе описываются механизмы IPC (Inter Process Communication) - семафоры, разделяемая память и очередь сообщений, реализованные в ядре Linux 2.4. Данная глава разбита на 4 раздела. В первых трех разделах рассматривается реализация семафоров, очереди сообщений, и разделяемой памяти соответственно. В последнем разделе описывается набор общих, для всех трех вышеуказанных механизмов, функций и структур данных.
Функции, описываемые в данном разделе, реализуют механизм семафоров пользовательского уровня. Обратите внимание на то, что реализация пользовательских семафоров основывается на использовании блокировок и семафоров ядра. Во избежание неоднозначности в толковании под термином "семафор ядра" будут пониматься семафоры уровня ядра. Во всех других случаях под термином "семафор" следует понимать семафоры пользовательского уровня.
Вызов sys_semget() защищен глобальным семафором ядра sem_ids.sem.
Для создания и инициализации нового набора семафоров вызывается функция newary(). В вызывающую программу возвращается ID нового набора семафоров.
Если через аогумент key передается существующий набор семафоров, вызывается функция поиска ipc_findkey() заданного набора. Перед возвратом ID проверяются права доступа вызывающей программы.
Для выполнения команд 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.
После проверки входных параметров данные копируются из пространства пользователя во временный буфер. Если объем информации невелик, то буфер размещается на стеке, в противном случае в памяти размещается буфер большего размера. После копирования данных выполняется глобальная блокировка семафоров, проверяется 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(), чтобы перевести задачу в разряд
"спящих".
После пробуждения задача повторно выполняет глобальную блокировку семафора, определяет причину пробуждения и реагирует на нее соответствующим образом:
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 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 { int semval; /* текущее значение */ int sempid; /* pid последней операции */ };
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 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 * 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; /* признак изменения семафора */ };
/* системный вызов semop берет массив отсюда. */ struct sembuf { unsigned short sem_num; /* индекс семафора в массиве */ short sem_op; /* операция */ short sem_flg; /* флаги */ };
/* Каждая задача имеет список откатов. Откаты выполняются автоматически * по завершении процесса. */ struct sem_undo { struct sem_undo * proc_next; /* следующий элемент списка для данного процесса */ struct sem_undo * id_next; /* следующий элемент в данном наборе семафоров */ int semid; /* ID набора семафоров */ short * semadj; /* массив изменений, по одному на семафор */ };
Следующие функции используются исключительно для поддержки механизма семафоров:
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() вызывается из semctl_down() для выполнения действий, перечисленных ниже. Вызов функции осуществляется под глобальной блокировкой семафоров, возврат управления происходит со снятой блокировкой.
Функция semctl_down() предназначена для выполнения операций IPC_RMID и IPC_SET системного вызова semctl(). Перед выполнением этих операций проверяется ID набора семафоров и права доступа. Обе эти операции выполняются под глобальной блокировкой семафоров.
Операция IPC_RMID вызывает freeary() для удаления набора семафоров.
Операция IPC_SET изменяет элементы uid
,
gid
, mode
и ctime
в наборе
семафоров.
Функция semctl_nolock() вызывается из sys_semctl() для выполнения операций IPC_INFO, SEM_INFO и SEM_STAT.
Операции IPC_INFO и SEM_INFO заполняют временный буфер seminfo статическими данными. Затем под
глобальной блокировкой семафора ядра sem_ids.sem
заполняются элементы semusz
и semaem
структуры seminfo в соответствии с требуемой
операцией (IPC_INFO или SEM_INFO) и в качестве результата
возвращается максимальный ID.
Операция SEM_STAT инициализирует временный буфер semid64_ds. На время копирования
значений sem_otime
, sem_ctime
, и
sem_nsems
в буфер выполняется глобальная блокировка
семафора. И затем данные копируются в пространство
пользователя.
Функция semctl_main() вызывается из sys_semctl() для выполнения ряда операций, которые описаны ниже. Перед выполнением операций, semctl_main() блокирует семафор и проверяет ID набора семафоров и права доступа. Перед возвратом блокировка снимается.
Операция GETALL загружает текущие значения семафора во временный буфер ядра и затем копирует его в пространство пользователя. При небольшом объеме данных, временный буфер размещается на стеке, иначе блокировка временно сбрасывается, чтобы распределить в памяти буфер большего размера. Копирование во временный буфер производится под блокировкой.
Операция SETALL копирует значения семафора из пользовательского пространства во временный буфер и затем в набор семафоров. На время копирования из пользовательского пространства во временный буфер и на время проверки значений блокировка сбрасывается. При небольшом объеме данных, временный буфер размещается на стеке, иначе в памяти размещается буфер большего размера. На время выполнения следующих действий блокировка восстанавливается:
sem_ctime
для набора
семафоров.Операция IPC_STAT копирует значения sem_otime
,
sem_ctime
и sem_nsems
во временный буфер
на стеке. После снятия блокировки данные копируются в
пользовательское пространство.
Операция GETVAL возвращает значение заданного семафора.
Операция GETPID возвращает pid
последней операции,
выполненной над семафором.
Операция GETNCNT возвращает число процессов, ожидающих на семафоре, когда тот станет меньше нуля. Это число подсчитывается функцией count_semncnt().
Операция GETZCNT возвращает число процессов, ожидающих на семафоре, когда тот станет равным нулю. Это число подсчитывается функцией count_semzcnt().
Проверяет новое значение семафора и выполняет следующие действия:
sem_ctime
.count_semncnt() возвращает число процессов, ожидающих на семафоре, когда тот станет меньше нуля.
count_semzcnt() возвращает число процессов, ожидающих на семафоре, когда тот станет равным нулю.
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() вызывается из sys_semop() и update_queue() и пытается выполнить каждую из операций в последовательности.
Если была встречена заблокированная операция, то процесс исполнения последовательности прерывается и все операции "откатываются". Если последовательность имела флаг IPC_NOWAIT, то возвращается код ошибки -EAGAIN. Иначе возвращается 1 для индикации того, что последовательность операций заблокирована.
Если значение семафора вышло за рамки системных ограничений, то выполняется "откат" всех операций и возвращается код ошибки -ERANGE.
Если последовательность операций была успешно выполнена и при
этом аргумент do_undo
не равен нулю, то выполняется
"откат" всех операций и возвращается 0. Если аргумент
do_undo
равен нулю, то результат операций остается в
силе и обновляется поле sem_otime
.
Функция sem_revalidate() вызывается, когда глобальная блокировка семафора временно была снята и необходимо снова ее получить. Вызывается из semctl_main() и alloc_undo(). Производит проверку ID семафора и права доступа, в случае успеха выполняет глобальную блокировку семафора.
Функция freeundos() проходит по списку "откатов" процесса в поисках заданной структуры. Если таковая найдена, то она изымается из списка и память, занимаемая ею, освобождается. В качестве результата возвращается указатель на следующую структуру "отката"в списке.
Вызов функции alloc_undo() должен производиться под установленной глобальной блокировкой семафора. В случае возникновения ошибки - функция завершает работу со снятой блокировкой.
Перед тем как вызовом kmalloc() распределить память под структуру sem_undo и массив корректировок, блокировка снимается. Если память была успешно выделена, то она восстанавливается вызовом sem_revalidate().
Далее новая структура инициализируется, указатель на структуру размещается по адресу, указанному вызывающей программой, после чего структура вставляется в начало списка "откатов" текущего процесса.
Функция sem_exit() вызывается из do_exit() и отвечает за выполнение всех "откатов" по завершении процесса.
Если процесс находится в состоянии ожидания на семафоре, то он удаляется из списка sem_queue при заблокированном семафоре.
Производится просмотр списка "откатов" текущего процесса и для каждого элемента списка выполняются следующие действия:
sem_otime
в наборе
семафоров.По окончании обработки списка очищается поле current->semundo.
На входе в sys_msgget() захватывается глобальный семафор очереди сообщений ( msg_ids.sem).
Для создания новой очереди сообщений вызывается функция newque(), которая создает и инициализирует новую очередь и возвращает ID новой очереди.
Если значение параметра key представляет существующую очередь, то вызывается ipc_findkey() для поиска соответствующего индекса в глобальном массиве дескрипторов очередей сообщений (msg_ids.entries). перед возвратом ID очереди производится проверка параметров и прав доступа. И поиск и проверки выполняются под блокировкой (msg_ids.ary).
Функции sys_msgctl() передаются следующие параметры: ID очереди
сообщений (msqid
), код операции (cmd
) и
указатель на буфер в пользовательском пространстве типа msgid_ds (buf
). Функция
принимает шесть кодов операций: IPC_INFO, MSG_INFO,IPC_STAT,
MSG_STAT, IPC_SET и IPC_RMID. После проверки ID очереди и кода
операции выполняются следующие действия:
Глобальная информация очереди сообщений копируется в пользовательское пространство.
Инициализируется временный буфер типа struct msqid64_ds и выполняется глобальная блокировка очереди сообщений. После проверки прав доступа вызывающего процесса во временный буфер записывается информация о заданной очереди и глобальная блокировка очереди сообщений освобождается. Содержимое буфера копируется в пользовательское пространство вызовом copy_msqid_to_user().
Пользовательские данные копируются через вызов copy_msqid_to_user(). Производится захват глобального семафора очереди сообщений и устанавливается блокировка очереди, которые в конце отпускаются. После проверки ID очереди и прав доступа текущего процесса, производится обновление информации. Далее, вызовом expunge_all() и ss_wakeup() активируются все процессы-получатели и процессы-отправители, находящиеся в очередях ожидания msq->q_receivers и msq->q_senders соответственно.
Захватывается глобальный семафор очереди сообщений и устанавливается глобальная блокировка очереди сообщений. После проверки ID очереди и прав доступа текущего процесса вызывается функция freeque(), которая освобождает ресурсы, занятые очередью. После этого глобальный семафор и глобальная блокировка отпускаются.
Функция sys_msgsnd() принимает через входные параметры ID
очереди сообщений (msqid
), указатель на буфер типа struct msg_msg (msgp
),
размер передаваемого сообщения (msgsz
) и флаг -
признак разрешения перехода процесса в режим ожидания
(msgflg
). Каждая очередь сообщений имеет две очереди
ожидания для процессов и одну очередь ожидающих сообщений. Если в
очереди ожидания имеется процесс, ожидающий данное сообщение
(msgp
), то это сообщение передается непосредственно
ожидающему процессу, после чего процесс-получатель
"пробуждается". В противном случае производится проверка
- достаточно ли места в очереди ожидающих сообщений и если
достаточно, то сообщение сохраняется в этой очереди. Если же места
недостаточно, то процесс-отправитель ставится в очередь ожидания.
Более подробное освещение этих действий приводится ниже:
msg
типа struct msg_msg. Инициализируются поле
типа сообщения и поле размера сообщения.msgflg
)
то глобальная блокировка очереди сообщений снимается, память,
занимаемая сообщением, освобождается и возвращается код
ошибки EAGAIN.msg
помещается в
очередь ожидающих сообщений (msq->q_messages). Обновляются
поля q_cbytes
и q_qnum
в дескрипторе
очереди сообщений, а так же глобальные переменные
msg_bytes
и msg_hdrs
, содержащие в себе
общий объем сообщений в байтах и общее количество сообщений.q_lspid
и q_stime
в дескрипторе очереди
и освобождается глобальная блокировка.На вход функции sys_msgrcv() передаются ID очереди
(msqid
), указатель на буфер типа msg_msg (msgp
),
предполагаемый размер сообщения (msgsz
), тип сообщения
(msgtyp
) и флаги (msgflg
). Функция, по
очереди ожидающих сообщений, ищет сообщение с заданным типом и
первое же найденное сообщение копирует в пользовательский буфер.
Если сообщения с заданным типом не обнаружено, то
процесс-получатель заносится в очередь ожидания для
процессов-получателей и остается там до тех пор, пока не будет
получено ожидаемое сообщение. Более подробное описание действий
функции sys_msgrcv() приводится ниже:
msgtyp
. Далее
выполняется глобальная блокировка очереди сообщений и находится
дескриптор очереди по заданному ID. Если искомая очередь
сообщений не найдена, то возвращается код ошибки EINVAL.msgtyp
.msgflg
. Если установлен флаг IPC_NOWAIT, то
глобальная блокировка снимается и вызывающему процессу
возвращается код ENOMSG. В противном случае процесс помещается
в очередь ожидания для процессов-получателей:
msr
и добавляется в начало очереди
ожидания.r_tsk
в msr
заносится
указатель на текущий процесс.r_msgtype
и r_mode
заносятся ожидаемый тип сообщения и режим поиска
соответственно.msgsz
, в противном случае
- значение INT_MAX.r_msg
заносится признак того, что
сообщение не найдено.r_msg
. Это поле содержит либо сообщение
переданное напрямую, либо код ошибки. Если поле содержит
сообщение то далее переходим к заключительным операциям (к п. 10). В
противном случае - опять выполняется глобальная блокировка.r_msg
проверяется еще раз. Если в процессе установки
блокировки было получено сообщение, то производится переход к заключительным операциям (к п. 10).r_msg
осталось без изменений, то,
следовательно, процесс был активирован для выполнения повторной
попытки получить сообщение. Проверяется наличие необработанных
сигналов для данного процесса и если таковые имеются, то
глобальная блокировка снимается и процессу возвращается код
EINTR. Иначе - производится повторная попытка получить сообщение.r_msg
содержит код ошибки, то
снимается глобальная блокировка и процессу передается
ошибка.msp
, тип сообщения записывается в
mtype
и содержимое сообщения копируется в поле
mtext
функцией store_msg(). И в заключение освобождается
память вызовом функции free_msg().Структуры данных механизма очередей сообщений определены в msg.c.
/* по одной структуре 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 list_head m_list; long m_type; int m_ts; /* размер сообщения */ struct msg_msgseg* next; /* Далее следует само сообщение */ };
/* сегмент сообщения на каждое сообщение */ struct msg_msgseg { struct msg_msgseg* next; /* Далее следует остальная часть сообщения */ };
/* по одной структуре msg_sender на каждый ожидающий процесс-отправитель */ struct msg_sender { struct list_head list; struct task_struct* tsk; };
/* по одной структуре 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 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 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 последнего процесса-получателя */ };
struct msq_setbuf { unsigned long qbytes; uid_t uid; gid_t gid; mode_t mode; };
Функция newque() размещает в памяти новый дескриптор очереди сообщений (struct msg_queue) и вызывает ipc_addid(), которая резервирует элемент массива очередей сообщений за новым дескриптором. Дескриптор очереди сообщений инициализируется следующим образом:
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() предназначена для удаления очереди сообщений. Функция полагает, что блокировка очереди сообщений уже выполнена. Она освобождает все ресурсы, связанные с данной очередью. Сначала вызывается ipc_rmid() (через msg_rmid()) для удаления дескриптора очереди из массива дескрипторов. Затем вызывается expunge_all для активизации процессов-получателей и ss_wakeup() для активизации процессов-отправителей, находящихся в очередях ожидания. Снимается блокировка очереди. Все сообщения из очереди удаляются и освобождается память, занимаемая дескриптором очереди.
Функция ss_wakeup() активизирует все процессы-отправители, стоящие в заданной очереди ожидания. Если функция вызывается из freeque(), то процессы исключаются из очереди ожидания.
Функция ss_add() принимает в качестве входных параметров
указатель на дескриптор очереди сообщений и указатель на структуру
msg_sender. Она заносит в поле tsk
указатель на
текущий процесс, изменяет статус процесса на TASK_INTERRUPTIBLE
после чего вставляет структуру msg_sender в начало очереди ожидания
процессов-отправителей заданной очереди сообщений.
Удаляет процесс-отправитель из очереди ожидания.
В функцию expunge_all() передаются дескриптор очереди сообщений
(msq
) и целочисленное значение (res
)
которое определяет причину активизации процесса-получателя. Для
каждого процесса-получателя из соответствующей очереди ожидания в
поле r_msg
заносится число res
, после
чего процесс активируется. Эта функция вызывается в случае
ликвидации очереди сообщений или в случае выполнения операций
управления очередью сообщений.
Всякий раз, когда процесс передает сообщение, из функции sys_msgsnd(), вызывается load_msg(), которая копирует сообщение из пользовательского пространства в пространство ядра. В пространстве ядра сообщение представляется как связный список блоков данных. В первом блоке размещается структура msg_msg. Размер блока данных, ассоциированного со структурой msg_msg, ограничен числом DATA_MSG_LEN. Блок данных и структура размещаются в непрерывном куске памяти, который не может быть больше одной страницы памяти. Если все сообщение не умещается в первый блок, то в памяти размещаются дополнительные блоки и связываются в список. Размер дополнительных блоков ограничен числом DATA_SEG_LEN и каждый из низ включает в себя структуру msg_msgseg) и связанный блок данных. Блок данных и структура msg_msgseg размещаются в непрерывном куске памяти, который не может быть больше одной страницы памяти. В случае успеха функция возвращает адрес новой структуры msg_msg.
Функция store_msg() вызывается при передаче сообщения в пользовательское пространство. Данные, описываемые структурами msg_msg и msg_msgseg последовательно копируются в пользовательский буфер.
Функция free_msg() освобождает память, занятую сообщением (структурой msg_msg и сегментами сообщения).
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() проверяет сообщение на соответсвтвие заданным критериям. Возвращает 1, если следующие условия соблюдены:
pipelined_send() позволяет процессам передать сообщение напрямую
процессам-получателям минуя очередь ожидания. Вызывает testmsg() в процессе помска
процесса-получателя, ожидающего данное сообщение. Если таковой
найден, то он удаляется из очереди ожидания для
процессов-получателей и активируется. Сообщение передается процессу
через поле r_msg
получателя. Если сообщение было
передано получателю, то функция >pipelined_send() возвращает 1.
Если процесса-получателя для данного сообщения не нашлось, то
возвращается 0.
Если в процессе поиска обнаруживаются получатели, заявившие
размер ожидаемого сообщения меньше имеющегося, то такие процессы
изымаются из очереди, активируются и им передается код E2BIG через
поле r_msg
. Поиск продолжается до тех пор пока не
будет найден процесс-получатель, соответствующий всем требованиям,
либо пока не будет достигнут конец очереди ожидания.
copy_msqid_to_user() копирует содержимое буфера ядра в пользовательский буфер. На входе получает пользовательский буфер, буфер ядра типа msqid64_ds и флаг версии IPC. Если флаг имеет значение IPC_64 то копирование из буфера ядра в пользовательский буфер производится напрямую, в противном случае инициализируется временный буфер типа msqid_ds и данные из буфера ядра переносятся во временный буфер, после чего содержимое временного буфера копируется в буфер пользователя.
Функция copy_msqid_from_user() получает на входе буфер ядра для
сообщения типа struct msq_setbuf, пользовательский буфер и флаг
версии IPC. В случае IPC_64, copy_from_user() производит
копирование данных из пользовательского буфера во временный буфер
типа msqid64_ds, после этого заполняются
поля qbytes
,uid
, gid
и
mode
в буфере ядра в соответствии со значениями в
промежуточном буфере. В противном случае, в качестве временного
буфера используется struct msqid_ds.
Вызов sys_shmget() регулируется глобальным семаформ разделяемой памяти.
В случае необходимости создания нового сегмента разделяемой памяти, вызывается функция newseg(), которая создает и инициализирует новый сегмент. ID нового сегмента передается в вызывающую программу.
В случае, когда значение входного параметра key соответствует существующему сегменту, то отыскивается соответствующий индекс в массиве дескрипторов и перед возвратом ID сегмента разделяемой памяти производится проверка входных параметров и прав доступа вызывающего процесса. Поиск и проверка производятся под глобальной блокировкой разделяемой памяти.
Временный буфер shminfo64 заполняется соответствующими значениями и затем копируется в пользовательское пространство вызвавшего приложения.
На время сбора статистической информации по разделяемой памяти,
производится захват глобального семафора и глобальной блокировки
разделяемой памяти. Для подсчета количества страниц, резмещенных
резидентно в памяти и количества страниц на устройстве свопинга,
вызывается shm_get_stat(). Дополнительно
подсчитываются общее количество страниц разделяемой памяти и
количество используемых сегментов. Количество
swap_attempts
и swap_successes
жестко
зашито в 0. Эти статистики заносятся во временный буфер shm_info и затем копируются в
пользовательское пространство вызывающего приложения.
Для выполнения 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, проверяются права доступа вызывающей программы. Требуемые статистики загружаются во временный буфер и затем передаются в вызывающую программу.
После проверки прав доступа выполняется глобальная блокировка разделяемой памяти и проверяется ID сегмента разделяемой памяти. Для выполнения обеих операций вызывается shmem_lock(). Параметры, передаваемые функции shmem_lock() однозначно определяют выполняемую операцию.
В ходе выполнения этой операции, глобальный семафор разделяемой памяти и глобальная блокировка удерживаются постоянно. Проверяется ID и затем, если в настоящий момент нет соединений с разделяемой памятью - вызывается shm_destroy() для ликвидации сегмента. Иначе - устанавливается флаг SHM_DEST, чтобы пометить сегмент как предназначенный к уничтожению и флаг IPC_PRIVATE, чтобы исключить возможность получения ссылки на ID из других процессов.
После проверки ID сегмента разделяемой памяти и прав доступа,
uid
, gid
и флаги mode
сегмента модифицируются данными пользователя. Так же обновляется и
поле shm_ctime
. Все изменения производятся после
захвата глобального семафора разделяемой памяти и при установленной
блокировке.
Функция 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() приобретается глобальный
семафор разделяемой памяти. В структуре mm_struct
текущего процесса отыскивается vm_area_struct
,
ассоциированная с заданным адресом разделяемой памяти. Если таковая
найдена, то вызывается do_munmap(), чтобы отменить отображение
сегмента разделяемой памяти в виртуальные адреса.
Важно так же то, что do_munmap() вызывает shm_close(), которая освобождает ресурсы, занятые сегментом разделяемой памяти, если не было выполнено других присоединений.
sys_shmdt() всегда возвращает 0.
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 { 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 /* 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 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 { 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() вызывается, когда возникает необходимость в
создании нового сегмента разделяемой памяти. В функцию передаются
три параметра - ключ (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() в цикле просматривает все дескрипторы сегментов разделяемой памяти и подсчитывает общее количество страниц, занятых разделяемой памятью и общее количество страниц разделяемой памяти, вытесненных на устройство свопинга. Так как получение данных связано с обращением к inode, то перед обращением к каждому inode выполняется блокировка, которая затем, после получения данных из inode, сразу же снимается.
shmem_lock() принимает в качестве параметров указатель на дескриптор сегмента разделяемой памяти и флаг требуемой операции - блокирование или разблокирование. Состояние блокировки запоминается в соответствующем inode. Если предыдущее состояние блокировки совпадает с требуемым, то shmem_lock() просто возвращает управление не производя дополнительных действий.
Состояние блокировки изменяется только после получения семафора на доступ к inode. Ниже описана последовательность действий, выполняемых над каждой страницей памяти в сегменте:
В результате исполнения shm_destroy() общее количество страниц,
занятых разделяемой памятью, уменьшается на количество страниц,
занятых удаляемым сегментом. Затем вызывается ipc_rmid() (через shm_rmid()) для
удаления ID сегмента Страницы памяти в сегменте разблокируются
функцией shmem_lock. Счетчик ссылок каждой страницы
устанавливается в 0. Вызывается fput(), чтобы уменьшить счетчик
f_count
соответствующего файла. И в заключение
вызывается kfree() для освобождения памяти под дескриптором
сегмента.
shm_inc() устанавливает PID, текущее время и увеличивает счетчик подключений для заданного сегмента разделяемой памяти. Эти действия выполняются после выполнения глобальной блокировки разделяемой памяти.
Функция shm_close() обновляет содержимое полей
shm_lprid
и shm_dtim
и уменьшает счетчик
подключений. Если счетчик обнулился, то вызывается shm_destroy() для освобождения ресурсов,
занятых сегментом разделяемой памяти. Эти действия выполняются
после выполнения глобальной блокировки и получения глобального
семафора разделяемой памяти.
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(). И в случае
успешного выполнения всех операций возвращает новый файловый
дескриптор.
Механизмы семафоров, очередей сообщений и разделяемой памяти в Linux основаны на наборе общих примитивов. Этот раздел посвящен их описанию.
Если запрошен размер памяти больше чем PAGE_SIZE, то вызывает vmalloc(), иначе - kmalloc() с флагом GFP_KERNEL.
Когда создается новый набор семафоров, очередь сообщений или сегмент разделяемой памяти, ipc_addid() сначала вызывает grow_ary(), чтобы расширить соответствующий массив дескрипторов, если это необходимо. Затем в массиве дескрипторов первый неиспользуемый элемент. Если таковой найден, то увеличивается счетчик используемых дескрипторов. Затем инициализирует структуру kern_ipc_perm и возвращает индекс нового дескриптора. В случае успеха возврат производится под глобальной блокировкой заданного типа IPC.
ipc_rmid() удаляет дескриптор из массива дескрипторов заданного типа, уменьшает счетчик используемых ID и, в случае необходимости, корректирует значение максимального ID. Возвращает указатель на дескриптор, соответствующий заданному ID.
ipc_buildid() создает уникальный ID для дескриптора заданного типа. ID создается в момент добавления нового элемента IPC (например нового сегмента разделяемой памяти или нового набора семафоров). ID достаточно просто преобразуется в индекс массива дескрипторов. Каждый тип IPC имеет свой порядковый номер, который увеличивается каждый раз, когда добавляется новый дескрипторо. ID создается путем умножения порядкового номера на SEQ_MULTIPLIER и добавления к результату индекса дескриптора в массиве. Этот порядковый номер запоминается в соответствующем дескрипторе.
ipc_checkid() делит заданный ID на SEQ_MULTIPLIER и сравнивает со значением seq в дескрипторе. Если они равны, то ID признается достоверным и функция возвращает 1, в противном случае возвращается 0.
grow_ary() предоставляет возможность динамического изменения максимального числа идентификаторов (ID) для заданного типа IPC. Однако текущее значение максимального числа идентификаторовне может превышать системного ограничения (IPCMNI). Если настоящий размер массива дескрипторов достаточно велик, то просто возвращает его текущий размер, иначе создает новый массив большего размера, копирует в него данные из старого массива, после чего память под старым массивом освобождается. На время переназначения массива дескрипторов выполняется глобальная блокировка для заданного типа IPC.
ipc_findkey() ищет в массиве дескрипторов объекта ipc_ids заданный ключ. В случае успеха возвращает индекс соответствующего дескриптора, в противном случае возвращает -1.
ipcperms() проверяет uid, gid и другие права доступа к ресурсу IPC. Возвращает 0 если доступ разрешен и -1 - в противном случае.
ipc_lock() выполняет глобальную блокировку заданного типа IPC и возвращает указатель на дескриптор, соответствующий заданному ID.
ipc_unlock() разблокирует заданный тип IPC.
ipc_lockall() выполняет глобальную блокировку требуемого механизма IPC (т.е. разделяемой памяти, семафоров и очередей сообщений).
ipc_unlockall() снимает глобальную блокировку с требуемого механизма IPC (т.е. разделяемой памяти, семафоров и очередей сообщений).
ipc_get() по заданному указателю на механизм IPC (т.е. разделяемая память, очереди сообщений или семафоры) и ID возвращает укзатель на соответствующий дескриптор IPC. Обратите внимание, что хотя различные механизмы IPC используют различные типы данных, тем не менее в каждом из них первым элементом указана общая для всех структура kern_ipc_perm. Возвращаемое функцией ipc_get() значение имеет именно этот общий тип данных. Как правило ipc_get() вызывается через функции-обертки (например shm_get()), которые выполняют приведение типов.
ipc_parse_version() сбрасывает флаг IPC_64 во входном параметре, если он был установлен, и возвращает либо IPC_64, либо IPC_OLD.
Каждый из дескрипторов 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; };
Структура 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; };
Массив структур ipc_id имеется в каждом экземпляре ipc_ids. Массив размещается динамически и размер его может быть изменен функцией grow_ary(). Иногда этот массив упоминается как массив дескрипторов, так как тип данных kern_ipc_perm используется универсальным функциями IPC для доступа к дескрипторам.
struct ipc_id { struct kern_ipc_perm* p; };