The design of the unix operating system by Maurice J

Вид материалаРеферат
Подобный материал:
1   ...   43   44   45   46   47   48   49   50   ...   55

бит MSG_NOERROR), ядро обрезает сообщение, возвращает запрошенное

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


-------------------------------------------------------------┐

│ #include

│ #include

│ #include

│ │

│ #define MSGKEY 75 │

│ │

│ struct msgform { │

│ long mtype; │

│ char mtext[256]; │

│ }; │

│ │

│ main() │

│ { │

│ struct msgform msg; │

│ int msgid,pid,*pint; │

│ │

│ msgid = msgget(MSGKEY,0777); │

│ │

│ pid = getpid(); │

│ pint = (int *) msg.mtext; │

│ *pint = pid; /* копирование идентификатора │

│ * процесса в область текста │

│ * сообщения */ │

│ msg.mtype = 1; │

│ │

│ msgsnd(msgid,&msg,sizeof(int),0); │

│ msgrcv(msgid,&msg,256,pid,0); /* идентификатор │

│ * процесса используется в │

│ * качестве типа сообщения */ │

│ printf("клиент: получил от процесса с pid %d\n", │

│ *pint); │

│ } │

L-------------------------------------------------------------


Рисунок 11.6. Пользовательский процесс


Процесс может получать сообщения определенного типа, если

присвоит параметру type соответствующее значение. Если это поло-

жительное целое число, функция возвращает первое значение данного

типа, если отрицательное, ядро определяет минимальное значение

типа сообщений в очереди, и если оно не превышает абсолютное зна-

чение параметра type, возвращает процессу первое сообщение этого

типа. Например, если очередь состоит из трех сообщений, имеющих

тип 3, 1 и 2, соответственно, а пользователь запрашивает сообще-

ние с типом -2, ядро возвращает ему сообщение типа 1. Во всех

случаях, если условиям запроса не удовлетворяет ни одно из сооб-

щений в очереди, ядро переводит процесс в состояние приостанова,

разумеется если только в параметре flag не установлен бит

IPC_NOWAIT (иначе процесс немедленно выходит из функции).

Рассмотрим программы, представленные на Рисунках 11.6 и 11.8.

Программа на Рисунке 11.8 осуществляет общее обслуживание запро-

сов пользовательских процессов (клиентов). Запросы, например, мо-

гут касаться информации, хранящейся в базе данных; обслуживающий

процесс (сервер) выступает необходимым посредником при обращении

к базе данных, такой порядок облегчает поддержание целостности

данных и организацию их защиты от несанкционированного доступа.

Обслуживающий процесс создает сообщение путем установки флага IPC

_CREAT при выполнении функции msgget и получает все сообщения ти-


-------------------------------------------------------------┐

│ алгоритм msgrcv /* получение сообщения */ │

│ входная информация: (1) дескриптор сообщения │

│ (2) адрес массива, в который заносится│

│ сообщение │

│ (3) размер массива │

│ (4) тип сообщения в запросе │

│ (5) флаги │

│ выходная информация: количество байт в полученном сообщении│

│ { │

│ проверить права доступа; │

│ loop: │

│ проверить правильность дескриптора сообщения; │

│ /* найти сообщение, нужное пользователю */ │

│ если (тип сообщения в запросе == 0) │

│ рассмотреть первое сообщение в очереди; │

│ в противном случае если (тип сообщения в запросе > 0) │

│ рассмотреть первое сообщение в очереди, имеющее │

│ данный тип; │

│ в противном случае /* тип сообщения в запросе < 0 */│

│ рассмотреть первое из сообщений в очереди с наи- │

│ меньшим значением типа при условии, что его тип │

│ не превышает абсолютное значение типа, указанно-│

│ го в запросе; │

│ если (сообщение найдено) │

│ { │

│ переустановить размер сообщения или вернуть ошиб-│

│ ку, если размер, указанный пользователем слишком│

│ мал; │

│ скопировать тип сообщения и его текст из прост- │

│ ранства ядра в пространство задачи; │

│ разорвать связь сообщения с очередью; │

│ вернуть управление; │

│ } │

│ /* сообщений нет */ │

│ если (флаги не разрешают приостанавливать работу) │

│ вернуть управление с ошибкой; │

│ приостановиться (пока сообщение не появится в очере- │

│ ди); │

│ перейти на loop; │

│ } │

L-------------------------------------------------------------


Рисунок 11.7. Алгоритм получения сообщения


па 1 - запросы от процессов-клиентов. Он читает текст сообщения,

находит идентификатор процесса-клиента и приравнивает возвращае-

мое значение типа сообщения значению этого идентификатора. В дан-

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

процессу-клиенту его идентификатор, и клиент получает сообщения с

типом, равным идентификатору клиента. Таким образом, обслуживаю-

щий процесс получает сообщения только от клиентов, а клиент -

только от обслуживающего процесса. Работа процессов реализуется в

виде многоканального взаимодействия, строящегося на основе одной

очереди сообщений.


-------------------------------------------------------------┐

│ #include

│ #include

│ #include

│ │

│ #define MSGKEY 75 │

│ struct msgform │

│ { │

│ long mtype; │

│ char mtext[256]; │

│ }msg; │

│ int msgid; │

│ │

│ main() │

│ { │

│ int i,pid,*pint; │

│ extern cleanup(); │

│ │

│ for (i = 0; i < 20; i++) │

│ signal(i,cleanup); │

│ msgid = msgget(MSGKEY,0777│IPC_CREAT); │

│ │

│ for (;;) │

│ { │

│ msgrcv(msgid,&msg,256,1,0); │


│ pint = (int *) msg.mtext; │

│ pid = *pint; │

│ printf("сервер: получил от процесса с pid %d\n",│

│ pid); │

│ msg.mtype = pid; │

│ *pint = getpid(); │

│ msgsnd(msgid,&msg,sizeof(int),0); │

│ } │

│ } │

│ │

│ cleanup() │

│ { │

│ msgctl(msgid,IPC_RMID,0); │

│ exit(); │

│ } │

L-------------------------------------------------------------


Рисунок 11.8. Обслуживающий процесс (сервер)


Сообщения имеют форму "тип - текст", где текст представляет

собой поток байтов. Указание типа дает процессам возможность вы-

бирать сообщения только определенного рода, что в файловой систе-

ме не так легко сделать. Таким образом, процессы могут выбирать

из очереди сообщения определенного типа в порядке их поступления,

причем эта очередность гарантируется ядром. Несмотря на то, что

обмен сообщениями может быть реализован на пользовательском уров-

не средствами файловой системы, представленный вашему вниманию

механизм обеспечивает более эффективную организацию передачи дан-

ных между процессами.

С помощью системной функции msgctl процесс может запросить

информацию о статусе дескриптора сообщения, установить этот ста-

тус или удалить дескриптор сообщения из системы. Синтаксис вызова

функции:

msgctl(id,cmd,mstatbuf)

где id - дескриптор сообщения, cmd - тип команды, mstatbuf - ад-

рес пользовательской структуры, в которой будут храниться управ-

ляющие параметры или результаты обработки запроса. Более подробно

об аргументах функции пойдет речь в Приложении.

Вернемся к примеру, представленному на Рисунке 11.8. Обслужи-

вающий процесс принимает сигналы и с помощью функции cleanup уда-

ляет очередь сообщений из системы. Если же им не было поймано ни

одного сигнала или был получен сигнал SIGKILL, очередь сообщений

остается в системе, даже если на нее не ссылается ни один из про-

цессов. Дальнейшие попытки исключительно создания новой очереди

сообщений с данным ключом (идентификатором) не будут иметь успех

до тех пор, пока старая очередь не будет удалена из системы.


11.2.2 Разделение памяти


Процессы могут взаимодействовать друг с другом непосредствен-

но путем разделения (совместного использования) участков вирту-

ального адресного пространства и обмена данными через разделяемую

память. Системные функции для работы с разделяемой памятью имеют

много сходного с системными функциями для работы с сообщениями.

Функция shmget создает новую область разделяемой памяти или возв-

ращает адрес уже существующей области, функция shmat логически

присоединяет область к виртуальному адресному пространству про-

цесса, функция shmdt отсоединяет ее, а функция shmctl имеет дело

с различными параметрами, связанными с разделяемой памятью. Про-

цессы ведут чтение и запись данных в области разделяемой памяти,

используя для этого те же самые машинные команды, что и при рабо-

те с обычной памятью. После присоединения к виртуальному адресно-

му пространству процесса область разделяемой памяти становится

доступна так же, как любой участок виртуальной памяти; для досту-

па к находящимся в ней данным не нужны обращения к каким-то до-

полнительным системным функциям.

Синтаксис вызова системной функции shmget:

shmid = shmget(key,size,flag);

где size - объем области в байтах. Ядро использует key для веде-

ния поиска в таблице разделяемой памяти: если подходящая запись

обнаружена и если разрешение на доступ имеется, ядро возвращает

вызывающему процессу указанный в записи дескриптор. Если запись

не найдена и если пользователь установил флаг IPC_CREAT, указыва-

ющий на необходимость создания новой области, ядро проверяет на-

хождение размера области в установленных системой пределах и вы-

деляет область по алгоритму allocreg (раздел 6.5.2). Ядро

записывает установки прав доступа, размер области и указатель на

соответствующую запись таблицы областей в таблицу разделяемой па-

мяти (Рисунок 11.9) и устанавливает флаг, свидетельствующий о

том, что с областью не связана отдельная память. Области выделя-

ется память (таблицы страниц и т.п.) только тогда, когда процесс

присоединяет область к своему адресному пространству. Ядро уста-

навливает также флаг, говорящий о том, что по завершении послед-

него связанного с областью процесса область не должна освобож-

даться. Таким образом, данные в разделяемой памяти остаются в

сохранности, даже если она не принадлежит ни одному из процессов

(как часть виртуального адресного пространства последнего).

Таблица раз- Таблица процессов -

деляемой па- Таблица областей частная таблица об-

мяти ластей процесса

-----------┐ ---------------┐ ----------┐

│ ----+----┐ │ │ -----+---- │

+----------+ -│->+--------------+<----- +---------+

│ ----+----│ │ │ ----+---- │

+----------+ │ +--------------+<----┐│ +---------+

│ ----+--┐ │ │ │ L│---+---- │

+----------+ │ │ +--------------+ │ +---------+

│ │ │ │ │ │ │ │ │

│ │ │ L->+--------------+ │ +---------+

│ │ │ │ │ │ │ │

│ │ L--->+--------------+<------ +---------+

│ │ │ │ (после │ │

│ │ +--------------+ shmat) +---------+

│ │ │ │ │ │

│ │ │ │ +---------+

│ │ │ │ │ │

│ │ │ │ │ │

│ │ L--------------- │ │

│ │ │ │

│ │ │ │

│ │ │ │

│ │ │ │

L----------- L----------

Рисунок 11.9. Структуры данных, используемые при разделении

памяти


Процесс присоединяет область разделяемой памяти к своему вир-

туальному адресному пространству с помощью системной функции

shmat:

virtaddr = shmat(id,addr,flags);

Значение id, возвращаемое функцией shmget, идентифицирует область

разделяемой памяти, addr является виртуальным адресом, по которо-

му пользователь хочет подключить область, а с помощью флагов

(flags) можно указать, предназначена ли область только для чтения

и нужно ли ядру округлять значение указанного пользователем адре-

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

виртуальный адрес, по которому ядро произвело подключение области

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

В начале выполнения системной функции shmat ядро проверяет

наличие у процесса необходимых прав доступа к области (Рисунок

11.10). Оно исследует указанный пользователем адрес; если он ра-

вен 0, ядро выбирает виртуальный адрес по своему усмотрению.


-------------------------------------------------------------┐

│ алгоритм shmat /* подключить разделяемую память */ │

│ входная информация: (1) дескриптор области разделяемой │

│ памяти │

│ (2) виртуальный адрес для подключения │

│ области │

│ (3) флаги │

│ выходная информация: виртуальный адрес, по которому область│

│ подключена фактически │

│ { │

│ проверить правильность указания дескриптора, права до- │

│ ступа к области; │

│ если (пользователь указал виртуальный адрес) │

│ { │

│ округлить виртуальный адрес в соответствии с фла- │

│ гами; │

│ проверить существование полученного адреса, размер│

│ области; │

│ } │

│ в противном случае /* пользователь хочет, чтобы ядро │

│ * само нашло подходящий адрес */ │

│ ядро выбирает адрес: в случае неудачи выдается │

│ ошибка; │

│ присоединить область к адресному пространству процесса │

│ (алгоритм attachreg); │

│ если (область присоединяется впервые) │

│ выделить таблицы страниц и отвести память под нее │

│ (алгоритм growreg); │

│ вернуть (виртуальный адрес фактического присоединения │

│ области); │

│ } │

L-------------------------------------------------------------

Рисунок 11.10. Алгоритм присоединения разделяемой памяти


Область разделяемой памяти не должна пересекаться в виртуаль-

ном адресном пространстве процесса с другими областями; следова-

тельно, ее выбор должен производиться разумно и осторожно. Так,

например, процесс может увеличить размер принадлежащей ему облас-

ти данных с помощью системной функции brk, и новая область данных

будет содержать адреса, смежные с прежней областью; поэтому, ядру

не следует присоединять область разделяемой памяти слишком близко

к области данных процесса. Так же не следует размещать область

разделяемой памяти вблизи от вершины стека, чтобы стек при своем

последующем увеличении не залезал за ее пределы. Если, например,

стек растет в направлении увеличения адресов, лучше всего размес-

тить область разделяемой памяти непосредственно перед началом об-

ласти стека.

Ядро проверяет возможность размещения области разделяемой па-

мяти в адресном пространстве процесса и присоединяет ее с помощью

алгоритма attachreg. Если вызывающий процесс является первым про-

цессом, который присоединяет область, ядро выделяет для области

все необходимые таблицы, используя алгоритм growreg, записывает

время присоединения в соответствующее поле таблицы разделяемой

памяти и возвращает процессу виртуальный адрес, по которому об-

ласть была им подключена фактически.

Отсоединение области разделяемой памяти от виртуального ад-

ресного пространства процесса выполняет функция

shmdt(addr)

где addr - виртуальный адрес, возвращенный функцией shmat. Нес-

мотря на то, что более логичной представляется передача идентифи-

катора, процесс использует виртуальный адрес разделяемой памяти,

поскольку одна и та же область разделяемой памяти может быть

подключена к адресному пространству процесса несколько раз, к то-

му же ее идентификатор может быть удален из системы. Ядро произ-

водит поиск области по указанному адресу и отсоединяет ее от ад-

ресного пространства процесса, используя алгоритм detachreg (раз-

дел 6.5.7). Поскольку в таблицах областей отсутствуют обратные

указатели на таблицу разделяемой памяти, ядру приходится просмат-

ривать таблицу разделяемой памяти в поисках записи, указывающей

на данную область, и записывать в соответствующее поле время пос-

леднего отключения области.

Рассмотрим программу, представленную на Рисунке 11.11. В ней

описывается процесс, создающий область разделяемой памяти разме-

ром 128 Кбайт и дважды присоединяющий ее к своему адресному

пространству по разным виртуальным адресам. В "первую" область он

записывает данные, а читает их из "второй" области. На Рисунке

11.12 показан другой процесс, присоединяющий ту же область (он

получает только 64 Кбайта, таким образом, каждый процесс может

использовать разный объем области разделяемой памяти); он ждет

момента, когда первый процесс запишет в первое принадлежащее об-

ласти слово любое отличное от нуля значение, и затем принимается

считывать данные из области. Первый процесс делает "паузу"

(pause), предоставляя второму процессу возможность выполнения;

когда первый процесс принимает сигнал, он удаляет область разде-

ляемой памяти из системы.

Процесс запрашивает информацию о состоянии области разделяе-

мой памяти и производит установку параметров для нее с помощью

системной функции shmctl:

shmctl(id,cmd,shmstatbuf);

Значение id идентифицирует запись таблицы разделяемой памяти, cmd

определяет тип операции, а shmstatbuf является адресом пользова-

тельской структуры, в которую помещается информация о состоянии

области. Ядро трактует тип операции точно так же, как и при уп-

равлении сообщениями. Удаляя область разделяемой памяти, ядро ос-

вобождает соответствующую ей запись в таблице разделяемой памяти

и просматривает таблицу областей: если область не была присоеди-

нена ни к одному из процессов, ядро освобождает запись таблицы и

все выделенные области ресурсы, используя для этого алгоритм

freereg (раздел 6.5.6). Если же область по-прежнему подключена к

каким-то процессам (значение счетчика ссылок на нее больше 0),

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

последнего связанного с нею процесса область не должна освобож-

даться. Процессы, уже использующие область разделяемой памяти,

продолжают работать с ней, новые же процессы не могут присоеди-

нить ее. Когда все процессы отключат область, ядро освободит ее.

Это похоже на то, как в файловой системе после разрыва связи с

файлом процесс может вновь открыть его и продолжать с ним работу.

11.2.3 Семафоры

Системные функции работы с семафорами обеспечивают синхрони-

зацию выполнения параллельных процессов, производя набор действий

единственно над группой семафоров (средствами низкого уровня). До

использования семафоров, если процессу нужно было заблокировать

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

creat специального блокирующего файла. Если файл уже существовал,

функция creat завершалась неудачно, и процесс делал вывод о том,

что ресурс уже заблокирован другим процессом. Главные недостатки

такого подхода заключались в том, что процесс не знал, в какой

момент ему следует предпринять следующую попытку, а также в том,

что блокирующие файлы случайно оставались в системе в случае ее


-------------------------------------------------------------┐

│ #include

│ #include

│ #include

│ #define SHMKEY 75 │

│ #define K 1024 │

│ int shmid; │

│ │

│ main() │

│ { │

│ int i, *pint; │