Уильям Стивенс - UNIX: взаимодействие процессов
22-24 Главный поток сервера блокируется при вызове pause. Вся функциональность обеспечивается функцией servproc, которая будет запускаться как отдельный поток каждый раз при получении запроса клиента.
Запустим сервер в отдельном окне:
solaris % server1 /tmp/server1
После этого запустим пpoгрaммy-клиeнт в другом окне, указав в качестве аргумента то же полное имя, которое было указано при вызове сервера:
solaris % client1 /tmp/server19
result: 81
solaris % ls -l /tmp/server1
Drw-r-r– 1 rstevens other1 0 Apr 9 10:09 /tmp/server1
Мы получили ожидаемый результат. Вызвав ls, мы видим, что эта пpoгрaммa выводит букву D в начале строки, соответствующей файлу, указывая, что этот файл является дверью.
На рис. 15.2 приведена диaгрaммa работы данного примера. Функция door_call вызывает процедуру на сервере, которая затем вызывает door_return для возврата.
На рис. 15.3 приведена диaгрaммa, показывающая, что в действительности происходит при вызове процедуры в другом процессе на том же узле.
Рис. 15.2. Внешний вид вызова процедуры в другом процессе
На рис. 15.3 выполняются следующие действия:
0. Запускается сервер, вызывает door_create, чтобы создать дескриптор для функции servproc, затем связывает этот дескриптор с именем файла в файловой системе.
1. Запускается клиент и вызывает door_call. Это функция в библиотеке дверей.
2. Библиотечная функция door_call делает системный вызов. При этом указывается процедура, которая должна быть выполнена, а управление передается функции из библиотеки дверей процесса-сервера.
3. Вызывается процедура сервера (servproc в данном примере).
4. Процедура сервера делает все необходимое для обработки запроса клиента и вызывает door_return по завершении работы.
5. Библиотечная функция door_return осуществляет системный вызов, передавая управление ядру.
6. В этом вызове указывается процесс-клиент, которому и передается управление.
Рис. 15.3. Что в действительности происходит при вызове процедуры в другом процессе
Последующие разделы этой главы описывают интерфейс дверей (doors API) более подробно, с множеством примеров. В приложении А мы убедимся, что двери представляют собой наиболее быструю форму IPC (при измерении времени ожидания).
15.2. Функция door_call
Функция door_call вызывается клиентом для обращения к процедуре сервера, выполняемой в адресном пространстве процесса-сервера:
#include <door.h>
int door_call(int fd, door_arg_t *argp);
/* Возвращает 0 в случае успешного завершения. –1 – в случае ошибки */
Дескриптор fd обычно возвращается функцией open (см. листинг 15.1). Полное имя файла, открываемого клиентом, однозначно идентифицирует процедуру сервера, которая вызывается door_call при передаче дескриптора.
Второй аргумент — argp — указывает на структуру, описывающую аргументы и приемный буфер для возвращаемых значений:
typedef struct door_arg {
char *data_ptr; /* при вызове указывает на аргументы, при возврате – на результаты */
size_t data_size; /* при вызове определяет общий размер аргументов в байтах, при возврате – общий размер возвращаемых данных в байтах */
door_desc_t *desc_ptr; /* при вызове указывает на аргументы-дескрипторы, при возврате указывает на возвращаемые дескрипторы */
size_t desc_num; /* при вызове задает количество аргументов-дескрипторов, при возврате задает количество возвращаемых дескрипторов */
char *rbuf; /* указатель на буфер результатов */
size_t rsize; /* размер буфера результатов */
} door_arg_t;
При возврате из удаленного вызова эта структура описывает возвращаемые значения. Все поля структуры могут быть изменены при возврате, мы подробно рассмотрим это ниже.
ПРИМЕЧАНИЕ
Использование типа char* для двух указателей кажется странным и требует использования явного преобразования типов для предотвращения вывода предупреждений компилятора. Естественно было бы использовать указатели типа void*. С указателями char* мы еще столкнемся в функции door_return. Вероятно, в Solaris 2.7 тип данных desc_num изменится на unsigned int и последний аргумент door_return изменится соответствующим образом.
Аргументы и результаты могут быть двух типов: данные и дескрипторы.
■ Аргументы-данные представляют собой последовательность данных длиной data_size байт. На эту последовательность должен указывать data_ptr. Клиент и сервер должны заранее знать формат этих данных (и аргументов, и результатов). Нет способа указать серверу тип аргументов. В пpoгрaммax листингов 15.1 и 15.2 клиент и сервер были написаны таким образом, что они оба знали, что аргумент представлял собой одно длинное целое и возвращаемый результат также был одним длинным целым. Для скрытия внутреннего устройства передаваемых данных их можно объединить в структуру, что упростит работу тому, кто будет читать код несколько лет спустя. Итак, все аргументы можно заключить в одну структуру, результаты — в другую и обе их определить в одном заголовочном файле, используемом клиентом и сервером. Пример будет приведен в листингах 15.8 и 15.9. Если аргументов-данных нет, указатель data_ptr должен быть нулевым и размер данных data_size должен иметь значение 0.
ПРИМЕЧАНИЕ
Поскольку клиент и сервер работают с аргументами и результатами, помещаемыми в соответствующие буферы, компилировать их следует одним и тем же компилятором, поскольку разные компиляторы могут по-разному упорядочивать данные в структурах даже в одной и той же системе.
■ Аргументы-дескрипторы хранятся в массиве структур door_desc_t, каждая из которых содержит один передаваемый от клиента серверу дескриптор. Количество структур типа door_desc_t задается аргументом desc_num. (Мы описываем эту структуру и смысл «передачи дескриптора» в разделе 15.8.) Если аргументов-дескрипторов нет, следует передать нулевой указатель desc_ptr и присвоить полю desc_num значение 0.
■ При возврате из функции data_ptr указывает на результаты-данные, a data_size задает размер возвращаемых данных. Если никакие данные не возвращаются, data_size будет иметь значение 0, а значение указателя data_ptr следует игнорировать.
■ Функция может возвращать и дескрипторы, при этом desc_ptr указывает на массив структур типа door_desc_t, каждая из которых содержит один передаваемый сервером клиенту дескриптор. Количество возвращаемых структур типа door_desc_t хранится в поле desc_num. Если дескрипторы не возвращаются, значение desc_num будет равно 0, а указатель desc_ptr следует игнорировать.
Можно спокойно использовать один и тот же буфер для передаваемых аргументов и возвращаемых результатов. При вызове door_call и data_ptr, и desc_ptr могут указывать на буфер, указанный аргументом rbuf.
Перед вызовом door_call клиент устанавливает указатель rbuf на буфер для результатов, a rsize делает равным размеру буфера. После возвращения из функции и data_ptr, и desc_ptr будут указывать на этот буфер. Если он слишком мал для хранения результатов, возвращаемых сервером, библиотека дверей автоматически выделит новый буфер в адресном пространстве клиента с помощью mmap (раздел 12.2) и обновит значения rbuf и rsize соответствующим образом. Поля data_ptr и desc_ptr будут указывать на новый буфер. Клиент отвечает за то, чтобы обнаружить изменение этих указателей и впоследствии освободить занимаемую память вызовом munmap с аргументами rbuf и rsize. Пример будет приведен в листинге 15.4.
15.3. Функция door_create
Процесс-сервер определяет некоторую функцию как процедуру сервера вызовом door_create:
#include <door.h>
typedef void Door_server_proc(void *cookie, char *dataptr, size_t datasize, door_desc_t *descptr, size_t ndesc);
int door_create(Door_server_proc *proc, void *cookie, u_int attr);
/* Возвращает неотрицательный дескриптор в случае успешного завершения, –1 –в случае ошибки */
Здесь мы добавили наше собственное определение типа, что упрощает прототип функции. Это определение утверждает, что процедуры сервера (например, servproc в листинге 15.2) вызываются с пятью аргументами и ничего не возвращают.
Когда сервер вызывает door_create, первый аргумент (proc) указывает адрес процедуры сервера, которая будет вызываться через дескриптор двери, возвращаемый этим вызовом. При вызове процедуры сервера ее аргумент cookie содержит значение, передаваемое в качестве второго аргумента door_create. Это дает серверу возможность передавать процедуре какой-либо указатель каждый раз, когда эта процедура вызывается клиентом. Следующих четыре аргумента процедуры сервера — dataptr, datasize, descptr и ndesc — описывают аргументы-данные и аргументы-дескрипторы клиента. Они соответствуют первым четырем полям структуры door_arg_t, описанной в предыдущем разделе.