Уильям Стивенс - UNIX: взаимодействие процессов
23 При создании нового потока вызовом pthread_create его отмена по умолчанию разрешена. Если отмена потока разрешена и клиент прерывает вызов door_call в процессе его выполнения (что мы продемонстрируем в листинге 15.26), вызываются обработчики отмены потока, после чего он завершается. Если отмена потока отключена (как это делаем мы) и клиент прерывает работу в вызове door_call, процедура сервера спокойно завершает работу (поток не завершается), а результаты door_return просто сбрасываются. Поскольку серверный поток завершается, если происходит отмена потока, и поскольку процедура сервера может в этот момент выполнять какие-то действия (возможно, с заблокированными семафорами или блокировками), библиотека дверей на всякий случай отключает отмену всех создаваемых ею потоков. Если нам нужно, чтобы процедура сервера отменялась при досрочном завершении работы клиента, для этого потока следует включить возможность отмены и приготовиться обработать такую ситуацию.
ПРИМЕЧАНИЕ
Обратите внимание, что область выполнения PTHREAD_SCOPE_SYSTEM и неприсоединенность потока указываются как атрибуты при создании потока. А отмена потока может быть отключена только в процессе выполнения потока. Таким образом, хотя мы и отключаем отмену потока, он сам может ее включить и выключить тогда, когда потребуется.
Связывание потока с дверью24 Вызов door_bind позволяет добавить поток к пулу, связанному с дверью, дескриптор которой передается door_bind в качестве аргумента. Поскольку для этого нам нужно знать дескриптор двери, в этой версии сервера он является глобальной переменной.
Делаем поток доступным клиенту25 Мы делаем поток доступным клиенту вызовом door_return с двумя нулевыми указателями и нулевыми значениями длин буферов в качестве аргументов.
Процедура сервера приведена в листинге 15.19. Она идентична программе из листинга 15.6.
Листинг 15.19. Процедура сервера//doors/server6.c
1 #include "unpipc.h"
2 void
3 servproc(void *cookie, char *dataptr, size_t datasize,
4 door_desc_t *descptr, size_t ndesc)
5 {
6 long arg, result;
7 arg = *((long *) dataptr);
8 printf("thread id %ld, arg = %ldn", pr_thread_id(NULL), arg);
9 sleep(5);
10 result = arg * arg;
11 Door_return((char *)&result, sizeof(result), NULL, 0);
12 }
Чтобы продемонстрировать работу программы, запустим сервер:
solaris % server6 /tmp/door6
my_thread: created server thread 4
После запуска сервера и вызова door_create процедура создания сервера запускается в первый раз, хотя клиент мы еще не запустили. При этом создается первый поток, ожидающий запроса от первого клиента. Затем мы запускаем клиент три раза подряд:
solaris % client6 /tmp/door6 11
result: 121
solaris % client6 /tmp/door6 22
result: 484
solaris % client6 /tmp/door6 33
result: 1089
Посмотрим, что при этом выводит сервер. При поступлении первого запроса клиента создается новый поток (с идентификатором потока 5), а поток с номером 4 обслуживает все запросы клиентов. Библиотека дверей всегда держит один лишний поток наготове:
my_thread: created server thread 5
thread id 4, arg = 11
thread id 4, arg = 22
thread id 4, arg = 33
Запустим теперь три экземпляра клиента одновременно в фоновом режиме:
solaris % client6 /tmp/door6 44 &client6 /tmp/door6 55 &client6 /tmp/door6 66 &
[2] 4919
[3] 4920
[4] 4921
solaris % result: 1936
result: 4356
result: 3025
Посмотрев на вывод сервера, мы увидим, что было создано два новых потока (с идентификаторами 6 и 7) и потоки 4, 5 и 6 обслужили три запроса от клиентов:
thread id 4, arg = 44
my_thread: created server thread 6
thread id 5, arg = 66
my_thread: created server thread 7
thread id 6, arg = 55
15.10. Функции door_bind, door unbind и door_revoke
Рассмотрим еще три функции, дополняющие интерфейс дверей:
#include <door.h>
int door_bind(int fd);
int door_unbind(void);
int door_revoke(int fd);
/* Всe три возвращают 0 в случае успешного завершения, –1 – в случае ошибки */
Функция door_bind впервые появилась в листинге 15.18. Она связывает вызвавший ее поток с частным пулом сервера, относящимся к двери с дескриптором fd. Если вызвавший поток уже подключен к какой-либо другой двери, производится его неявное отключение.
Функция door_unbind осуществляет явное отключение потока от текущего пула, к которому он подключен.
Функция door_revoke отключает доступ к двери с дескриптором fd. Дескриптор двери может быть отменен только процессом, создавшим эту дверь. Все вызовы через эту дверь, находящиеся в процессе выполнения в момент вызова этой функции, будут благополучно завершены.
15.11. Досрочное завершение клиента или сервера
В наших примерах до настоящего момента предполагалось, что в процессе работы клиента и сервера не возникает непредусмотренных ситуаций. Посмотрим, что произойдет, если у клиента или сервера возникнут ошибки. В случае если клиент и сервер являются частями одного процесса (локальный вызов процедуры на рис. 15.1), клиенту не нужно беспокоиться о возникновении ошибок на сервере, и наоборот. Однако если клиент и сервер находятся в различных процессах, нужно учесть возможность досрочного завершения одного из них и предусмотреть способ уведомления второго об этом событии. Об этом нужно заботиться вне зависимости от того, находятся ли клиент и сервер на одном узле или нет.
Досрочное завершение сервера
Если клиент блокируется в вызове door_call, ожидая получения результатов, ему нужно каким-то образом получить уведомление о завершении потока сервера по какой-либо причине. Посмотрим, что происходит в этом случае, прервав работу сервера вызовом pthread_exit. Это приведет к завершению потока сервера (а не всего процесса). В листинге 15.20 приведен текст процедуры сервера.
Листинг 15.20. Процедура сервера, завершающая работу сразу после запуска//doors/serverintr1.c
1 #include "unpipc.h"
2 void
3 servproc(void *cookie, char *dataptr, size_t datasize,
4 door_desc_t *descptr, size_t ndesc)
5 {
6 long arg, result;
7 pthread_exit(NULL); /* посмотрим, что произойдет с клиентом */
8 arg = *((long*)dataptr);
9 result = arg * arg;
10 Door_return((char*)&result, sizeof(result), NULL, 0);
11 }
Оставшаяся часть сервера не претерпевает изменений по сравнению с листингом 15.2, а программу-клиент мы берем из листинга 15.1.
Запустив клиент, мы увидим, что вызов door_call возвращает ошибку EINTR, если процедура сервера завершается досрочно:
solaris % clientintr1 /tmp/door1 11
door_call error: Interrupted system call
Непрерываемость системного вызова door_call
Документация на door_call предупреждает, что эта функция не предполагает возможности перезапуска (библиотечная функция door_call делает системный вызов с тем же именем). Мы можем убедиться в этом, изменив процедуру сервера таким образом, чтобы она делала паузу в 6 секунд перед возвращением, что показано в листинге 15.21.
Листинг 15.21. Процедура сервера делает паузу в 6 секунд//doors/serverintr2.с
1 #include "unpipc.h"
2 void
3 servproc(void *cookie, char *dataptr, size_t datasize,
4 door_desc_t *descptr, size_t ndesc)
5 {
6 long arg, result;
7 sleep(6); /* клиент получает сигнал SIGCHLD */
8 arg = *((long*)dataptr);
9 result = arg * arg;
10 Door_return((char*)&result, sizeof(result), NULL, 0);
11 }
Изменим теперь клиент из листинга 15.2: установим обработчик сигнала SIGCHLD, добавив порождение процесса и завершение порожденного процесса через 2 секунды. Таким образом, через 2 секунды после вызова door_call дочерний процесс завершит работу, а родительский перехватит сигнал SIGCHLD и произойдет возврат из обработчика сигнала, прерывающий системный вызов door_call. Текст программы-клиента показан в листинге 15.22.
Листинг 15.22. Клиент, перехватывающий сигнал SIGCHLD//doors/clientintr2.c
1 #include "unpipc.h"
2 void
3 sig_chld(int signo)
4 {
5 return; /* просто прерываем door_call() */
6 }
7 int
8 main(int argc, char **argv)
9 {
10 int fd;
11 long ival, oval;
12 door_arg_t arg;
13 if (argc != 3)
14 err_quit("usage: clientintr2 <server-pathname> <integer-value>");
15 fd = Open(argv[1], O_RDWR); /* открываем дверь */
16 /* подготовка аргументов и указателя на результат */