Андрей Робачевский - Операционная система UNIX
Взаимодействие с прикладными процессами
Рассмотренный ранее программный интерфейс TLI полностью реализует функциональность TPI. Легко заметить соответствие между отдельными функциями TLI и примитивами TPI, приведенными в табл. 6.10. Схема вызова функций TPI и обмена соответствующими примитивами TPI между клиентом и сервером для типичного TCP-сеанса приведена на рис. 6.32.
Рис. 6.32. Функции TLI и примитивы TPI
Программный интерфейс потоков был рассмотрен в главе 5 при обсуждении подсистемы STREAMS. Основными функциями, обеспечивающими передачу и получение сообщений, являются системные вызовы putmsg(2) и getmsg(2). Таким образом, большинство функций TLI, составляющих программный интерфейс доступа прикладных процессов к транспортным протоколам, являются удобной оболочкой (реализованной в виде библиотеки, например, libnsl.so) более фундаментальным системным вызовам putmsg(2) и getmsg(2).
В качестве примера рассмотрим функцию t_connect(3N). Ее реализация может иметь следующий вид:
int t_connect(int fd, struct t_call *sndcall,
struct t_call *recvcall) {
struct T_conn_req *connreq;
struct T_conn_con* conncon;
struct T_ok_ack *okack;
struct T_error_ack *errack;
struct strbuf connect, ack, confirm, m_data;
struct netbuf addr, opt, udata;
char *buf;
int flags;
...
/* Сохраним адреса буферов netbuf запроса sndcall */
addr = sndcall->addr; opt = sndcall->opt;
udata = sndcall->udata;
/* Заполним поля структуры strbuf для формирования
управляющей части (блок M_PROTO) сообщения T_CONN_REQ */
connect.len =
sizeof(struct T_conn_req) + addr.len + opt.len;
connect.maxlen =
sizeof(struct Т_conn_req) + addr.maxlen + opt.maxlen;
buf = (char*)malloc(connect.maxlen);
connect.buf = buf;
/* Заполним поля заголовка блока M_PROTO сообщения T_CONN_REQ в
соответствии с форматом структуры T_conn_req */
connreq = (struct T_conn_req*)buf;
connreq->PRIM_type = T_CONN_REQ;
connreq->DEST_length = addr.len;
connreq->DEST_offset = sizeof (struct T_conn_req);
buf += sizeof(struct T_conn_req);
memcpy(buf, addr.buf, addr.len);
connreq->OPT_length = opt.len;
connreq->OPT_offset = connreq->DEST_offset + opt.len;
buf += addr.len;
memcpy(buf, opt.buf, opt.len);
/* Заполним поля структуры strbuf для формирования блока данных
(блок M_DATA) */
m_data.len = udata.len;
m_data.maxlen = udata.maxlen;
m_data.buf = udata.buf;
/* Отправим запрос Т_CONN_REQ поставщику транспортных услуг
по потоку fd */
putmsg(fd, &connect, &m_data, 0);
/* Подготовимся к приему подтверждения. Выделим максимальный
размер для получения негативного подтверждения, поскольку
примитив T_ERROR_ACK занимает больше места */
ack.len = ack.maxlen = sizeof(struct T_error_ack);
ack.buf = udata.buf;
/* Подтверждение является приоритетным, поэтому установим флаг
RS_HIPRI. До получения подтверждения не предпринимаем
никаких действий */
flags = RS_HIPRI;
getmsg(fd, &ack, (struct strbuf*)0, &flags);
free(connect.buf);
okack = (struct T_ok_ack*)ack.buf;
/* Проверим получено ли положительное или
негативное подтверждение */
if (okack->PRIM_type == T_OK_ACK) {
/* Если подтверждение положительное, подготовимся к получению
согласия удаленного пользователя на установление связи
(примитив T_CONN_CON) */
free(ack.buf);
if (recvcall != NULL) {
addr = recvcall->addr;
opt = recvcall->opt;
udata = recvcall->udata;
confirm.len = sizeof(struct T_conn_con) + addr.len + opt.len;
confirm.maxlen =
sizeof(struct T_conn_con) + addr.maxlen + opt.maxlen;
buf = (char*)malloc(confirm.maxlen);
confirm.buf = buf;
m_data.len = udata.len;
m_data.maxlen = udata.maxlen;
m_data.buf = udata.buf;
/* Получим примитив T_CONN_CON */
getmsg(fd, &confirm, &m_data, &flags);
free(buf);
conncon = (struct T_conn_con*)confirm.buf;
if (conncon->PRIM_type == T_CONN_CON) {
/* Если это действительно согласие, заполним
структуру rcvcall для пользователя TLI */
addr.len = conncon->OPT_length;
opt.len = conncon->OPT_length;
memcpy(addr.buf, conncon+conncon->RES_offset, addr.len);
memcpy(opt.buf, conncon+conncon->OPT_offset, opt.len);
free(confirm.buf);
/* Все закончилось удачно — возвращаем 0 */
return 0;
}
} else {
/* В случае отказа мы готовы обработать примитив
T_DISCON_IND */
...
return -1;
}
} else {
/* Если получен примитив T_ERROR_ACK — обработаем его */
errack = (struct T_error_ack*)ack.buf;
...
return -1;
}
}
Подобным образом реализовано большинство функций TLI. Заметим, что в конкретном случае использования транспортного протокола TCP прием и передача данных осуществляются в виде потока, не содержащего каких-либо логических записей. В этом случае не требуется формирование примитивов типа T_DATA_REQ и T_DATA_IND. В то же время, для передачи и получения экстренных данных будут использованы примитивы T_EXDATA_REQ и T_EXDATA_IND. При использовании протокола UDP все данные будут передаваться с помощью примитивов T_UNITDATA_REQ и T_UNITDATA_IND.
Описанная реализация программного интерфейса TLI имеет один существенный недостаток — операции функций не являются атомарными. Другими словами, выполнение функции t_connect(3N) может быть прервано другими процессами, которые могут также связываться с удаленным узлом. Это возможно, поскольку выполнение значительной части операций происходит в режиме задачи. Если для функции t_connect(3N) нарушение атомарности допустимо, то ряд функций, таких, например, как связывание (t_bind(3N)), получение информации (t_open(3N), t_getinfo(3N)) и установка или получение опций протокола (t_optmgmt(3N)) должны быть защищены от возможного нарушения целостности данных по причине прерывания операции. Единственным способом гарантировать атомарность является перевод выполнения критических участков (например, между отправлением примитива и получением подтверждения от поставщика транспортных услуг) в режим ядра. Для этого подсистема STREAMS предлагает механизм обмена управляющими командами с помощью вызова ioctl(2).
Однако с помощью ioctl(2), как было показано в разделе "Подсистема STREAMS" главы 5, можно формировать лишь сообщения типа M_IOCTL. Для преобразования этих сообщений в примитивы TPI служит дополнительный модуль timod(7M), встраиваемый в поток между головным и транспортным модулями. На рис. 6.33 показано местоположение модуля timod(7M) и схематически отображены его функции.
Рис. 6.33. Архитектура доступа к транспортным услугам
Для всех сообщений STREAMS, за исключением сообщений M_IOCTL, которые генерируются головным модулем в ответ на системный вызов ioctl(fd, I_STR, ...), модуль timod(7M) является прозрачным, т.е. он просто передает эти сообщения следующему модулю вниз по потоку без какой-либо обработки. Несколько сообщений M_IOCTL обрабатываются модулем и преобразуются в соответствующие примитивы TPI.
При этом вызов ioctl(2) имеет следующий формат:
#include <sys/stropts.h>
struct strioctl my_strioctl
...
strioctl.ic_cmd = cmd;
strioctl.ic_timeout = INFTIM;
strioctl.ic_len = size;
strioctl.ic_dp = (char*)buf;
ioctl(fd, I_STR, &my_strioctl);
При вызове ioctl(2) поле size устанавливается равным размеру соответствующего примитива TPI, определенного полем cmd и расположенного в буфере buf. При возврате из функции поле size содержит размер примитива, возвращенного поставщиком транспортных услуг и расположенного в буфере buf.
Модуль timod(7M) служит для обработки следующих команд cmd:
Значение cmd Обработка модулем timod(7M) TI_BIND Команда преобразуется в примитив T_BIND_REQ. При успешном завершении функции ioctl(2) в буфере buf находится примитив T_BIND_ACK. TI_UNBIND Команда преобразуется в примитив T_UNBIND_REQ. При успешном завершении функции ioctl(2) в буфере buf находится примитив T_OK_ACK. TI_GETINFO Команда преобразуется в примитив T_INFO_REQ. При успешном завершении функции ioctl(2) в буфере buf находится примитив T_INFO_ACK. TI_OPTMGMT Команда преобразуется в примитив T_OPTMT_REQ. При успешном завершении функции ioctl(2) в буфере buf находится примитив T_OPTMGMT_ACK.Интерфейс DLPI