| { |
| int i, *pint; |
| char *addr; |
| extern char *shmat(); |
| |
| shmid = shmget(SHMKEY,64*K,0777); |
| |
| addr = shmat(shmid,0,0); |
| pint = (int *) addr; |
| |
| while (*pint == 0) |
| ; |
| for (i = 0; i < 256, i++) |
| printf("%d\n",*pint++); |
| } |
+-----------------------------------------------------+

Рисунок 11.12. Разделение памяти между процессами



Таблица семафоров Массивы семафоров
+-------+
| | +---+---+---+---+---+---+---+
| +------->| 0 | 1 | 2 | 3 | 4 | 5 | 6 |
| | +---+---+---+---+---+---+---+
+-------+
| | +---+---+---+
| +------->| 0 | 1 | 2 |
| | +---+---+---+
+-------+
| | +---+
| +------->| 0 |
| | +---+
+-------+
| | +---+---+---+
| +------->| 0 | 1 | 2 |
| | +---+---+---+
+-------+
| - |
| - |
| - |
| - |
| - |
+-------+


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


346

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

id = semget(key,count,flag);

где key, flag и id имеют тот же смысл, что и в других механизмах взаимодейс-
твия процессов (обмен сообщениями и разделение памяти). В результате выпол-
нения функции ядро выделяет запись, указывающую на массив семафоров и содер-
жащую счетчик count (Рисунок 11.13). В записи также хранится количество се-
мафоров в массиве, время последнего выполнения функций semop и semctl. Сис-
темная функция semget на Рисунке 11.14, например, создает семафор из двух
элементов.
Синтаксис вызова системной функции semop:

oldval = semop(id,oplist,count);

где id - дескриптор, возвращаемый функцией semget, oplist - указатель на
список операций, count - размер списка. Возвращаемое функцией значение
oldval является прежним значением семафора, над

+------------------------------------------------------------+
| #include |
| #include |
| #include |
| |
| #define SEMKEY 75 |
| int semid; |
| unsigned int count; |
| /* определение структуры sembuf в файле sys/sem.h |
| * struct sembuf { |
| * unsigned shortsem_num; |
| * short sem_op; |
| * short sem_flg; |
| }; */ |
| struct sembuf psembuf,vsembuf; /* операции типа P и V */|
| |
| main(argc,argv) |
| int argc; |
| char *argv[]; |
| { |
| int i,first,second; |
| short initarray[2],outarray[2]; |
| extern cleanup(); |
| |
| if (argc == 1) |
| { |
| for (i = 0; i < 20; i++) |
| signal(i,cleanup); |
| semid = semget(SEMKEY,2,0777|IPC_CREAT); |
| initarray[0] = initarray[1] = 1; |
| semctl(semid,2,SETALL,initarray); |
| semctl(semid,2,GETALL,outarray); |
| printf("начальные значения семафоров %d %d\n", |
| outarray[0],outarray[1]); |
| pause(); /* приостанов до получения сигнала */ |
| } |
| |
| /* продолжение на следующей странице */ |
+------------------------------------------------------------+

Рисунок 11.14. Операции установки и снятия блокировки


347

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

+------------------------------------------------------------+
| else if (argv[1][0] == 'a') |
| { |
| first = 0; |
| second = 1; |
| } |
| else |
| { |
| first = 1; |
| second = 0; |
| } |
| |
| semid = semget(SEMKEY,2,0777); |
| psembuf.sem_op = -1; |
| psembuf.sem_flg = SEM_UNDO; |
| vsembuf.sem_op = 1; |
| vsembuf.sem_flg = SEM_UNDO; |
| |
| for (count = 0; ; count++) |
| { |
| psembuf.sem_num = first; |
| semop(semid,&psembuf,1); |
| psembuf.sem_num = second; |
| semop(semid,&psembuf,1); |
| printf("процесс %d счетчик %d\n",getpid(),count); |
| vsembuf.sem_num = second; |
| semop(semid,&vsembuf,1); |
| vsembuf.sem_num = first; |
| semop(semid,&vsembuf,1); |
| } |
| } |
| |
| cleanup() |
| { |
| semctl(semid,2,IPC_RMID,0); |
| exit(); |
| } |
+------------------------------------------------------------+

Рисунок 11.14. Операции установки и снятия блокировки (продолжение)


Ядро считывает список операций oplist из адресного пространства задачи и
проверяет корректность номеров семафоров, а также наличие у процесса необхо-
димых разрешений на чтение и корректировку семафоров (Рисунок 11.15). Если
таких разрешений не имеется, системная функция завершается неудачно. Если
ядру приходится приостанавливать свою работу при обращении к списку опера-
ций, оно возвращает семафорам их прежние значения и находится в состоянии
приостанова до наступления ожидаемого события, после чего систем-
ная функция запускается вновь. Поскольку ядро хранит коды операций над сема-
форами в глобальном списке, оно вновь считывает этот список из пространства


348

+------------------------------------------------------------+
| алгоритм semop /* операции над семафором */ |
| входная информация: (1) дескриптор семафора |
| (2) список операций над семафором |
| (3) количество элементов в списке |
| выходная информация: исходное значение семафора |
| { |
| проверить корректность дескриптора семафора; |
| start: считать список операций над семафором из простран- |
| ства задачи в пространство ядра; |
| проверить наличие разрешений на выполнение всех опера- |
| ций; |
| |
| для (каждой операции в списке) |
| { |
| если (код операции имеет положительное значение) |
| { |
| прибавить код операции к значению семафора; |
| если (для данной операции установлен флаг UNDO)|
| скорректировать структуру восстановления |
| для данного процесса; |
| вывести из состояния приостанова все процессы, |
| ожидающие увеличения значения семафора; |
| } |
| в противном случае если (код операции имеет отрица-|
| тельное значение) |
| { |
| если (код операции + значение семафора >= 0) |
| { |
| прибавить код операции к значению семафо- |
| ра; |
| если (флаг UNDO установлен) |
| скорректировать структуру восстанов- |
| ления для данного процесса; |
| если (значение семафора равно 0) |
| /* продолжение на следующей страни- |
| * це */ |
+------------------------------------------------------------+

Рисунок 11.15. Алгоритм выполнения операций над семафором


задачи, когда перезапускает системную функцию. Таким образом, операции вы-
полняются комплексно - или все за один сеанс или ни одной.
Ядро меняет значение семафора в зависимости от кода операции. Если код
операции имеет положительное значение, ядро увеличивает значение семафора и
выводит из состояния приостанова все процессы, ожидающие наступления этого
события. Если код операции равен 0, ядро проверяет значение семафора: если
оно равно 0, ядро переходит к выполнению других операций; в противном случае
ядро увеличивает число приостановленных процессов, ожидающих, когда значение
семафора станет нулевым, и "засыпает". Если код операции имеет отрицательное
значение и если его абсолютное значение не превышает значение семафора, ядро
прибавляет код операции (отрицательное число) к значению семафора. Если ре-
зультат равен 0, ядро выводит из состояния приостанова все процессы, ожидаю-
щие обнуления значения семафора. Если результат меньше абсолютного
значения кода операции, ядро приостанавливает процесс до тех пор, пока зна-
чение семафора не увеличится. Если процесс приостанавливается посреди опера-
ции, он имеет приоритет, допускающий прерывания; следовательно, получив сиг-
нал, он выходит из этого состояния.

349

+------------------------------------------------------------+
| вывести из состояния приостанова все |
| процессы, ожидающие обнуления значе-|
| ния семафора; |
| продолжить; |
| } |
| выполнить все произведенные над семафором в |
| данном сеансе операции в обратной последова- |
| тельности (восстановить старое значение сема- |
| фора); |
| если (флаги не велят приостанавливаться) |
| вернуться с ошибкой; |
| приостановиться (до тех пор, пока значение се- |
| мафора не увеличится); |
| перейти на start; /* повторить цикл с самого |
| * начала * / |
| } |
| в противном случае /* код операции равен нулю */|
| { |
| если (значение семафора отлично от нуля) |
| { |
| выполнить все произведенные над семафором |
| в данном сеансе операции в обратной по- |
| следовательности (восстановить старое |
| значение семафора); |
| если (флаги не велят приостанавливаться) |
| вернуться с ошибкой; |
| приостановиться (до тех пор, пока значение|
| семафора не станет нулевым); |
| перейти на start; /* повторить цикл */ |
| } |
| } |
| } /* конец цикла */ |
| /* все операции над семафором выполнены */ |
| скорректировать значения полей, в которых хранится вре-|
| мя последнего выполнения операций и идентификаторы |
| процессов; |
| вернуть исходное значение семафора, существовавшее в |
| момент вызова функции semop; |
| } |
+------------------------------------------------------------+

Рисунок 11.15. Алгоритм выполнения операций над семафором (продолжение)


Перейдем к программе, представленной на Рисунке 11.14, и предположим,
что пользователь исполняет ее (под именем a.out) три раза в следующем поряд-
ке:
a.out &
a.out a &
a.out b &
Если программа вызывается без параметров, процесс создает набор семафо-
ров из двух элементов и присваивает каждому семафору значение, равное 1. За-
тем процесс вызывает функцию pause и приостанавливается для получения сигна-
ла, после чего удаляет семафор из системы (cleanup). При выполнении програм-
мы с параметром 'a' процесс (A) производит над семафорами в цикле четыре
операции: он уменьшает на единицу значение семафора 0, то же самое делает с
семафором 1, выполняет команду вывода на печать и вновь увеличивает значения
семафоров 0 и 1. Если бы процесс попытался уменьшить значение семафора, рав-

350

ное 0, ему пришлось бы приостановиться, следовательно, семафор можно считать
захваченным (недоступным для уменьшения). Поскольку исходные значения сема-
форов были равны 1 и поскольку к семафорам не было обращений со стороны дру-
гих процессов, процесс A никогда не приостановится, а значения семафоров бу-
дут изменяться только между 1 и 0. При выполнении программы с параметром 'b'
процесс (B) уменьшает значения семафоров 0 и 1 в порядке, обратном ходу вы-
полнения процесса A. Когда процессы A и B выполняются параллельно, может
сложиться ситуация, в которой процесс A захватил семафор 0 и хочет захватить
семафор 1, а процесс B захватил семафор 1 и хочет захватить семафор 0. Оба
процесса перейдут в состояние приостанова, не имея возможности продолжить
свое выполнение. Возникает взаимная блокировка, из которой процессы могут
выйти только по получении сигнала.
Чтобы предотвратить возникновение подобных проблем, процессы могут вы-
полнять одновременно несколько операций над семафорами. В последнем примере
желаемый эффект достигается благодаря использованию следующих операторов:

struct sembuf psembuf[2];

psembuf[0].sem_num = 0;
psembuf[1].sem_num = 1;
psembuf[0].sem_op = -1;
psembuf[1].sem_op = -1;
semop(semid,psembuf,2);

Psembuf - это список операций, выполняющих одновременное уменьшение значений
семафоров 0 и 1. Если какая-то операция не может выполняться, процесс приос-
танавливается. Так, например, если значение семафора 0 равно 1, а значение
семафора 1 равно 0, ядро оставит оба значения неизменными до тех пор, пока
не сможет уменьшить и то, и другое.
Установка флага IPC_NOWAIT в функции semop имеет следующий смысл: если
ядро попадает в такую ситуацию, когда процесс должен приостановить свое вы-
полнение в ожидании увеличения значения семафора выше определенного уровня
или, наоборот, снижения этого значения до 0, и если при этом флаг IPC_NOWAIT
установлен, ядро выходит из функции с извещением об ошибке. Таким образом,
если не приостанавливать процесс в случае невозможности выполнения отдельной
операции, можно реализовать условный тип семафора.
Если процесс выполняет операцию над семафором, захватывая при этом неко-
торые ресурсы, и завершает свою работу без приведения семафора в исходное
состояние, могут возникнуть опасные ситуации. Причинами возникновения таких
ситуаций могут быть как ошибки программирования, так и сигналы, приводящие к
внезапному завершению выполнения процесса. Если после того, как процесс
уменьшит значения семафоров, он получит сигнал kill, восстановить прежние
значения процессу уже не удастся, поскольку сигналы данного типа не анализи-
руются процессом. Следовательно, другие процессы, пытаясь обратиться к сема-
форам, обнаружат, что последние заблокированы, хотя сам заблокировавший их
процесс уже прекратил свое существование. Чтобы избежать возникновения по-
добных ситуаций, в функции semop процесс может установить флаг SEM_UNDO;
когда процесс завершится, ядро даст обратный ход всем операциям, выполненным
процессом. Для этого в распоряжении у ядра имеется таблица, в которой каждо-
му процессу в системе отведена отдельная запись. Запись таблицы содержит
указатель на группу структур восстановле-
ния, по одной структуре на каждый используемый процессом семафор (Рисунок
11.16). Каждая структура восстановления состоит из трех элементов - иденти-
фикатора семафора, его порядкового номера в наборе и установочного значения.
Ядро выделяет структуры восстановления динамически, во время первого вы-
полнения системной функции semop с установленным флагом SEM_UNDO. При после-
дующих обращениях к функции с тем же флагом ядро просматривает структуры
восстановления для процесса в поисках структуры с тем же самым идентификато-


351

Заголовки частных структур
восстановления Структуры восстановления
+------+
| - |
| - |
| - |
| - | +----------+ +----------+ +----------+
+------+ |Дескриптор| |Дескриптор| |Дескриптор|
| +-->| Номер +-->| Номер +-->| Номер |
+------+ | Значение | | Значение | | Значение |
| | +----------+ +----------+ +----------+
| | +----------+
+------+ |Дескриптор|
| +-->| Номер |
+------+ | Значение |
| - | +----------+
| - |
| - |
| - |
+------+

Рисунок 11.16. Структуры восстановления семафоров


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

+---------------++-------+ +---------------++-------+-------+
| идентификатор || | | идентификатор || | |
| семафора || semid | | семафора || semid | semid |
+---------------++-------+ +---------------++-------+-------+
| номер семафора|| 0 | | номер семафора|| 0 | 1 |
+---------------++-------+ +---------------++-------+-------+
| установочное || | | установочное || | |
| значение || 1 | | значение || 1 | 1 |
+---------------++-------+ +---------------++-------+-------+

(а) После первой операции (б) После второй операции

+---------------++-------+
| идентификатор || |
| семафора || semid |
+---------------++-------+
| номер семафора|| 0 | пусто
+---------------++-------+
| установочное || |
| значение || 1 |
+---------------++-------+

(в) После третьей операции (г) После четвертой операции

Рисунок 11.17. Последовательность состояний списка структур восстановления


352



ет специальную процедуру, которая просматривает все связанные с процессом
структуры восстановления и выполняет над указанным семафором все обусловлен-
ные действия.
Ядро создает структуру восстановления всякий раз, когда процесс уменьша-
ет значение семафора, а удаляет ее, когда процесс увеличивает значение сема-
фора, поскольку установочное значение
структуры равно 0. На Рисунке 11.17 показана последовательность состояний
списка структур при выполнении программы с параметром 'a'. После первой опе-
рации процесс имеет одну структуру, состоящую из идентификатора semid, номе-
ра семафора, равного 0, и установочного значения, равного 1, а после второй
операции появляется вторая структура с номером семафора, равным 1, и устано-
вочным значением, равным 1. Если процесс неожиданно завершается, ядро прохо-
дит по всем структурам и прибавляет к каждому семафору по единице, восста-
навливая их значения в 0. В частном случае ядро уменьшает установочное зна-
чение для семафора 1 на третьей операции, в соответствии с увеличением зна-
чения самого семафора, и удаляет всю структуру целиком, поскольку установоч-
ное значение становится нулевым. После четвертой операции у процесса больше
нет структур восстановления, поскольку все установочные значения стали нуле-
выми.
Векторные операции над семафорами позволяют избежать взаимных блокиро-
вок, как было показано выше, однако они представляют известную трудность для
понимания и реализации, и в большинстве приложений полный набор их возмож-
ностей не является обязательным. Программы, испытывающие потребность в ис-
пользовании набора семафоров, сталкиваются с возникновением взаимных блоки-
ровок на пользовательском уровне, и ядру уже нет необходимости поддерживать
такие сложные формы системных функций.
Синтаксис вызова системной функции semctl:

semctl(id,number,cmd,arg);

Параметр arg объявлен как объединение типов данных:

union semunion {
int val;
struct semid_ds *semstat; /* описание типов см. в При-
* ложении */
unsigned short *array;
} arg;

Ядро интерпретирует параметр arg в зависимости от значения параметра
cmd, подобно тому, как интерпретирует команды ioctl (глава 10). Типы дейст-
вий, которые могут использоваться в параметре cmd: получить или установить
значения управляющих параметров (права доступа и др.), установить значения
одного или всех семафоров в наборе, прочитать значения семафоров. Подробнос-
ти по каждому действию содержатся в Приложении. Если указана команда удале-
ния, IPC_RMID, ядро ведет поиск всех процессов, содержащих структуры восста-
новления для данного семафора, и удаляет соответствующие структуры из систе-
мы. Затем ядро инициализирует используемые семафором структуры данных и вы-
водит из состояния приостанова все процессы, ожидающие наступления некоторо-
го связанного с семафором события: когда процессы возобновляют свое выполне-
ние, они обнаруживают, что идентификатор семафора больше не является коррек-
тным, и возвращают вызывающей программе ошибку.


    11.2.4 Общие замечания



Механизм функционирования файловой системы и механизмы взаимодействия

353

процессов имеют ряд общих черт. Системные функции типа "get" похожи на функ-
ции creat и open, функции типа "control" предоставляют возможность удалять
дескрипторы из системы, чем похожи на функцию unlink. Тем не менее, в меха-
низмах взаимодействия процессов отсутствуют операции, аналогичные операциям,
выполняемым системной функцией close. Следовательно, ядро не располагает
сведениями о том, какие процессы могут использовать механизм IPC, и, дейст-
вительно, процессы могут прибегать к услугам этого механизма, если правильно
угадывают соответствующий идентификатор и если у них имеются необходимые
права доступа, даже если они не выполнили предварительно функцию типа "get".
Ядро не может автоматически очищать неиспользуемые структуры механизма взаи-
модействия процессов, поскольку ядру неизвестно, какие из этих структур
больше не нужны. Таким образом, завершившиеся вследствие возникновения ошиб-
ки процессы могут оставить после себя ненужные и неиспользуемые структуры,
перегружающие и засоряющие систему. Несмотря на то, что в структурах меха-
низма взаимодействия после завершения существования процесса ядро может сох-
ранить информацию о состоянии и данные, лучше все-таки для этих целей ис-
пользовать файлы.
Вместо традиционных, получивших широкое распространение файлов механизмы
взаимодействия процессов используют новое пространство имен, состоящее из
ключей (keys). Расширить семантику ключей на всю сеть довольно трудно, пос-
кольку на разных машинах ключи могут описывать различные объекты. Короче го-
воря, ключи в основном предназначены для использования в одномашинных систе-
мах. Имена файлов в большей степени подходят для распределенных систем (см.
главу 13). Использование ключей вместо имен файлов также свидетельствует о
том, что средства взаимодействия процессов являются "вещью в себе", полезной
в специальных приложениях, но не имеющей тех возможностей, которыми облада-
ют, например, каналы и файлы. Большая часть функциональных возможностей,
предоставляемых данными средствами, может быть реализована с помощью других
системных средств, поэтому включать их в состав ядра вряд ли следовало бы.
Тем не менее, их использование в составе пакетов прикладных программ тесного
взаимодействия дает лучшие результаты по сравнению со стандартными файловыми
средствами (см. Упражнения).


    11.3 ВЗАИМОДЕЙСТВИЕ В СЕТИ



Программы, поддерживающие межмашинную связь, такие, как электронная поч-
та, программы дистанционной пересылки файлов и удаленной регистрации, издав-
на используются в качестве специальных средств организации подключений и ин-
формационного обмена. Так, например, стандартные программы, работающие в
составе электронной почты, сохраняют текст почтовых сообщений пользователя в
отдельном файле (для пользователя "mjb" этот файл имеет имя
"/usr/mail/mjb"). Когда один пользователь посылает другому почтовое сообще-
ние на ту же машину, программа mail (почта) добавляет сообщение в конец фай-
ла адресата, используя в целях сохранения целостности различные блокирующие
и временные файлы. Когда адресат получает почту, программа mail открывает
принадлежащий ему почтовый файл и читает сообщения. Для того, чтобы послать
сообщение на другую машину, программа mail должна в конечном итоге отыскать
на ней соответствующий почтовый файл. Поскольку программа не может работать
с удаленными файлами непосредственно, процесс, протекающий на другой машине,
должен действовать в качестве агента локального почтового процесса; следова-
тельно, локальному процессу необходим способ связи со своим удаленным аген-
том через межмашинные границы. Локальный процесс является клиентом удаленно-
го обслуживающего (серверного) процесса.
Поскольку в системе UNIX новые процессы создаются с помощью системной
функции fork, к тому моменту, когда клиент попытается выполнить подключение,
обслуживающий процесс уже должен существовать. Если бы в момент создания но-
вого процесса удаленное ядро получало запрос на подключение (по каналам меж-
машинной связи), возникла бы несогласованность с архитектурой системы. Чтобы

354

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