pid tid name prio STATE Blocked
4 1 devc-pty 10r RECEIVE 1
В приведенном примере сервер псевдотерминалов (называемый devc-pty
) имеет идентификатор процесса 4, содержит один поток с идентификатором потока 1, выполняется с приоритетом 10, подчиняется диспетчеризации карусельного типа (RR) и находится в состоянии блокировки по приему, ожидая сообщения по каналу с идентификатором 1 (к «каналам» мы еще скоро вернемся).
Смена состояний сервера.
По получении сообщения сервер переходит в состояние готовности (READY) и становится способен выполнять работу. Если оказывается, что из всех процессов, находящихся в данный момент в состоянии готовности (READY), наш сервер имеет наивысший приоритет, он получит процессор и сможет выполнить какие-то действия. Поскольку это сервер, он анализирует поступившее сообщение и решает, что с ним делать. В некоторый момент времени сервер завершит обработку сообщения и затем «ответит» клиенту.
Перейдем теперь к клиенту. Изначально клиент работал самостоятельно, пока не решил послать сообщение. Клиент переключается при этом из состояния готовности (READY) в состояние либо блокировки по передаче (send-blocked), либо блокировки по приему (recieve-blocked), в зависимости от состояния сервера, которому было послано сообщение.
Смена состояний клиента
Скорее всего, вам чаще придется иметь дело с состоянием блокировки по приему (reply-blocked), чем с состоянием блокировки по передаче (send-blocked). Состояние блокировки по приему (reply-blocked) реально означает следующее:
Сервер принял сообщение и теперь обрабатывает его. В некоторый момент времени сервер завершит обработку и ответит клиенту. Клиент блокирован в ожидании этого ответа от сервера.
Сравните с состоянием блокировки по передаче (send-blocked):
Сервер все еще не принял сообщение — вероятно, потому что был занят обработкой другого, ранее поступившего сообщения. Когда сервер возвратится в состояние «приема» вашего (клиентского) сообщения, вы перейдете из состояния блокировки по передаче (send-blocked) в состояние блокировки по ответу (reply- blocked).
На практике, если вы наблюдаете процесс, блокированный по передаче (send-blocked), это означает одно из двух:
1. Вы запечатлели момент, когда сервер был занят обслуживанием некоего клиента и в это время получил еще один запрос.
Это нормальная ситуация — вы можете проверить это, повторно выполнив pidin
. На сей раз вы, вероятно, сможете увидеть, что этот процесс уже более не блокирован по передаче.
2. В сервере проявилась какая-то внутренняя ошибка, и он больше не воспринимает запросы.
Когда это произойдет, вы сможете увидеть множество процессов, блокированных по передаче на этом сервере. Чтобы проверить это, выполните pidin
снова и посмотрите, есть ли изменения в состоянии клиентских процессов.
Ниже приведен пример, в котором показан клиент в состоянии блокировки по ответу (reply-blocked) и сервер, по которому он блокирован:
pid tid name prio STATE Blocked
1 1 /nto/x86/sys/procnto 0f READY
1 2 /nto/x86/sys/procnto 10r RECEIVE 1
1 3 /nto/x86/sys/procnto 10r NANOSLEEP
1 4 /nto/x86/sys/procnto 10r RUNNING
1 5 /nto/x86/sys/procnto 15r RECEIVE 1
16426 1 esh 10r REPLY 1
В примере показано, что программа esh
(встраиваемый командный интерпретатор) передала сообщение процессу с номером 1 (это ядро и администратор процессов, procnto
) и теперь ждет ответа.
Ну вот, теперь вы знаете основы обмена сообщениями в архитектуре «клиент/сервер».
Не исключено, что вы сейчас думаете: «Так что, получается, чтобы открыть файл или записать данные, мне придется писать специализированные вызовы обмена сообщениями QNX/ Neutrino?!»
Нет, вам не придется программировать обмен сообщениями непосредственно — разве что если вам будет нужно копнуть совсем вглубь (об этом несколько позже). Действительно, позвольте мне показать Вам некоторую программу клиента, который делает передачу сообщений:
#include <fcntl.h>
#include <unistd.h>
int main(void) {
int fd;
fd = open("filename", O_WRONLY);
write(fd, "Это обмен сообщениями\n", 24);
close(fd);
return (EXIT_SUCCESS);
}
Видите? Обычная Си-программа, никаких хитростей.
Собственно обмен сообщениями реализован в Си-библиотеке QNX/Neutrino. Вы просто выдаете вызовы по стандарту POSIX 1003.1 и вызовы функций ANSI Си и Си-библиотека делает за Вас всю работу, связанную с обменом сообщениями.
В приведенном выше примере вызываются три функции, и посылаются три различных сообщения:
• open() — передала сообщение «open» («открыть»);
• write() — передала сообщение «write» («записать»);
• close() — передала сообщение «close» («закрыть»).
Мы обсудим сами сообщения более подробно, когда мы будем изучать администраторы ресурсов (в главе «Администраторы ресурсов»), а пока что единственное, что нам надо знать об этом — это сам факт, что были переданы сообщения различных типов.
Давайте на мгновение отвлечемся и сравним этот подход с тем, как бы это работало в традиционной операционной системе.
Клиентская программа осталась бы такой же — различия были бы скрыты в Си-библиотеке, поставляемой производителем программного обеспечения. В такой системе функция open() сделала бы системный вызов, ядро затем обратилось бы непосредственно к файловой системе, которая, в свою очередь, выполнила некоторые действия и возвратила бы дескриптор файла. Вызовы функций write() и close() работали бы аналогично
Итак? Есть ли преимущества в способе, который предлагает QNX/Neutrino? «Оставайтесь с нами!»
Распределенный обмен сообщениями
Предположим, что мы пожелали изменить приведенный выше пример, чтобы можно было «поговорить» с другим узлом сети. Вы, наверное, думаете, что для этого придется вызывать специальные функции, чтобы «попасть в сеть». Вот сетевой вариант нашей программы:
#include <fcntl.h>
#include <unistd.h>
int main(void) {
int fd;
fd = open("/net/wintermute/home/rk/filename", O_WRONLY);
write(fd, "Это обмен сообщениями\n", 24);
close(fd);
return (EXIT_SUCCESS);
}
Вы будете правы, если скажете, что в обеих версиях программы почти одинаковы. Так и есть.
В традиционной ОС функция open() библиотеки Си вызывает ядро, которое анализирует имя файла и говорит: «Опа! Это не на нашем узле…» Ядро затем вызывает сетевую файловую систему NFS, которая уже определяет, где в действительности находится файл /net/wintermute/home/rk/filename
. Затем, NFS вызывает сетевой драйвер и посылает сообщение ядру на узле wintermute
, которое повторяет весь процесс, описанный нами в нашем первоначальном примере. Заметьте, что в этом случае оказываются вовлеченными две файловые системы, одна из которых — сетевая файловая система (NFS) клиента, а вторая — удаленная. К сожалению, в зависимости от реализации как удаленной файловой системы, так и NFS, некоторые операции (например, блокировки файлов) могут работать некорректно из-за неполной совместимости.