Основы операционных систем

Изучение теорий операционных систем. Исследование принципов построения и особенностей проектирования современных ОС. Сущность виртуальной памяти и алгоритма синхронизации. Рассмотрение операционной и файловой системы, система управления вводом-выводом.

Рубрика Программирование, компьютеры и кибернетика
Вид книга
Язык русский
Дата добавления 12.01.2010
Размер файла 2,6 M

Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже

Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.

* Служебная информация транспортных протоколов (UDP-заголовок к данным и TCP-заголовок к данным) должна содержать 16-битовые номера портов для сокета отправителя и сокета получателя. Добавление необходимой информации к данным при переходе от верхних уровней семейства протоколов к нижним принято называть английским словом encapsulation (дословно: герметизация). На рисунке 14-15.5 приведена схема encapsulation при использовании протокола UDP на сети Ethernet.

IP-

заголовок

UDP-заголовок

Данные

Тип пакета = IP 48 бит Ethernet-адрес отправителя 48 бит Ethernet-адрес получателя

Рис. 14-15.5. Encapsulation для UDP-протокола на сети Ethernet

Поскольку между МАС-адресами и IP-адресами существует взаимно однозначное соответствие, известное семейству протоколов TCP/IP, то фактически для полного задания адреса доставки и адреса отправления, необходимых для установления двусторонней связи, нужно указать пять параметров:

<транспортный протокол, IP-адрес отправителя, порт отправителя, IP-адрес получателя, порт получателях

Уровень приложений/процессов

К этому уровню можно отнести протоколы TFTP (Trivial File Transfer Protocol), FTP (File Transfer Protocol), telnet, SMTP (Simple Mail Transfer Protocol) и другие, которые поддерживаются соответствующими системными утилитами. Об их использовании подробно рассказано в UNIX Manual, и останавливаться на них мы не будем.

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

Использование модели клиент-сервер для взаимодействия удаленных процессов

В материалах семинара 9 при обсуждении мультиплексирования сообщений (раздел «Понятие мультиплексирования. Мультиплексирование сообщений. Модель взаимодействия процессов клиент--сервер. Неравноправность клиента и сервера») говорилось об использовании модели клиент -- сервер для организации взаимодействия локальных процессов. Эта же модель, изначально предполагающая неравноправность взаимодействующих процессов, наиболее часто используется для организации сетевых приложений. Напомним основные отличия процессов клиента и сервера применительно к удаленному взаимодействию:

Сервер, как правило, работает постоянно, на всем протяжении жизни приложения, а клиенты могут работать эпизодически.

Сервер ждет запроса от клиентов, инициатором же взаимодействия выступает клиент.

Как правило, клиент обращается к одному серверу за раз, в то время как к серверу могут одновременно поступить запросы от нескольких клиентов.

Клиент должен знать полный адрес сервера (его локальную и удаленную части) перед началом организации запроса (до начала общения), в то время как сервер может получить информацию о полном адресе клиента из пришедшего запроса (после начала общения).

И клиент, и сервер должны использовать один и тот же протокол транспортного уровня.

Неравноправность процессов в модели клиент--сервер, как мы увидим далее, накладывает свой отпечаток на программный интерфейс, используемый между уровнем приложений/процессов и транспортным уровнем.

Поступающие запросы сервер может обрабатывать последовательно -- запрос за запросом -- или параллельно, запуская для обработки каждого из них свой процесс или thread. Как правило, серверы, ориентированные на связь клиент--сервер с помощью установки логического соединения (TCP-протокол), ведут обработку запросов параллельно, а серверы, ориентированные на связь клиент--сервер без установления соединения (UDP-протокол), обрабатывают запросы последовательно.

Рассмотрим основные действия, которые нам необходимы в терминах абстракции socket для того, чтобы организовать взаимодействие между клиентом и сервером, используя транспортные протоколы стека TCP/IP.

Организация связи между удаленными процессами с помощью датаграмм

Как уже упоминалось в лекциях, более простой для взаимодействия удаленных процессов является схема организации общения клиента и сервера с помощью датаграмм, т. е. использование протокола UDP.

Рассмотрение этой схемы мы начнем с некоторой житейской аналогии, а затем убедимся, что каждому житейски обоснованному действию в операционной системе UNIX соответствует определенный системный вызов.

С точки зрения обычного человека общение процессов посредством датаграмм напоминает общение людей в письмах. Каждое письмо представляет собой законченное сообщение, содержащее адрес получателя, адрес отправителя и указания, кто написал письмо и кто должен его получить. Письма могут теряться, доставляться в неправильном порядке, быть поврежденными в дороге и т. д.

Что в первую очередь должен сделать человек, проживающий в отдаленной местности, для того чтобы принимать и отправлять письма? Он должен изготовить почтовый ящик, который одновременно будет служить и для приема корреспонденции, и для ее отправки. Пришедшие письма почтальон будет помещать в этот ящик и забирать из него письма, подготовленные к отправке.

Изготовленный почтовый ящик нужно где-то прикрепить. Это может быть парадная дверь дома или вход со двора, изгородь, столб, дерево и т. п. Потенциально может быть изготовлено несколько почтовых ящиков и размещено в разных местах с тем, чтобы письма от различных адресатов прибывали в различные ящики. Этим ящикам будут соответствовать разные адреса: «г. Иванову, почтовый ящик на конюшне», «г. Иванову, почтовый ящик, что на дубе».

После закрепления ящика мы готовы к обмену корреспонденцией. Человек-клиент пишет письмо с запросом по заранее известному ему адресу человека-сервера и ждет получения ответного письма. После получения ответа он читает его и перерабатывает полученную информацию.

Человек-сервер изначально находится в состоянии ожидания запроса. Получив письмо, он читает текст запроса и определяет адрес отправителя. После обработки запроса он пишет ответ и отправляет его по обратному адресу, после чего начинает ждать следующего запроса.

Все эти модельные действия имеют аналоги при общении удаленных процессов по протоколу UDP.

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

Процесс-клиент должен сначала совершить те же самые подготовительные действия: создать сокет и настроить его адрес. Затем он передает сообщение, указав, кому оно предназначено (IP-адрес сетевого интерфейса и номер порта сервера), ожидает от него ответа и продолжает свою деятельность.

Схематично эти действия выглядят так, как показано на рисунке 15-І6.6. Каждому из них соответствует определенный системный вызов. Названия вызовов написаны справа от блоков соответствующих действий.

Создание сокета производится с помощью системного вызова socket (). Для привязки созданного сокета к IP-адресу и номеру порта (настройка адреса) служит системный вызов bind (). Ожиданию получения информации, ее чтению и, при необходимости, определению адреса отправителя соответствует системный вызов recvfrom(). За отправку датаграммы отвечает системный вызов sendto ().

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

Сетевой порядок байт. Функции htonsQ, htonlQ, ntohsQ, ntohlQ

Передача от одного вычислительного комплекса к другому символьной информации, как правило (когда один символ занимает один байт), не вызывает проблем. Однако для числовой информации ситуация усложняется.

Как известно, порядок байт в целых числах, представление которых занимает более одного байта, может быть для различных компьютеров неодинаковым. Есть вычислительные системы, в которых старший байт числа имеет меньший адрес, чем младший байт (big-endian byte order), а есть вычислительные системы, в которых старший байт числа имеет больший адрес, чем младший байт (little-endian byte order). При передаче целой числовой информации от машины, имеющей один порядок байт, к машине с другим порядком байт мы можем неправильно истолковать принятую информацию. Для того чтобы этого не произошло, было введено понятие сетевого порядка байт, т. е. порядка байт, в котором должна представляться целая числовая информация в процессе передачи ее по сети (на самом деле -- это big-endian byte order). Целые числовые данные из представления, принятого на компьютере-отправителе, переводятся пользовательским процессом в сетевой порядок байт, путешествуют в таком виде по сети и переводятся в нужный порядок байт на машине-получателе процессом, которому они предназначены. Для перевода целых чисел из машинного представления в сетевое и обратно используется четыре функции: htons (), htonl (), ntohs(),ntohl().

Функции преобразования порядка байт Прототипы функций

#include <netinet/in.h>

unsigned long int htonl(unsigned long int hostlong); unsigned short int htons(unsigned short int hostshort); unsigned long int ntohl(unsigned long int netlong); unsigned short int ntohs(unsigned short int netshort);

Описание функций

Функция htonl осуществляет перевод целого длинного числа из порядка байт, принятого на компьютере, в сетевой порядок байт.

Функция htons осуществляет перевод целого короткого числа из порядка байт, принятого на компьютере, в сетевой порядок байт.

Функция ntohl осуществляет перевод целого длинного числа из сетевого порядка байт в порядок байт, принятый на компьютере.

Функция ntohs осуществляет перевод целого короткого числа из сетевого порядка байт в порядок байт, принятый на компьютере.

В архитектуре компьютеров і80x86 принят порядок байт, при котором младшие байты целого числа имеют младшие адреса. При сетевом порядке байт, принятом в Internet, младшие адреса имеют старшие байты числа.

Параметр у них -- значение, которое мы собираемся конвертировать. Возвращаемое значение -- то, что получается в результате конвертации. Направление конвертации определяется порядком букв h (host) и n (network) в названии функции, размер числа -- последней буквой названия, то есть htons -- это host to network short, ntohl -- network to host long.

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

Функции преобразования IP-адресов inet_ntoa(), inet_aton()

Нам также понадобятся функции, осуществляющие перевод IP-адресов из символьного представления (в виде четверки чисел, разделенных точками) в числовое представление и обратно. Функция inet_aton() переводит символьный IP-адрес в числовое представление в сетевом порядке байт.

Функция возвращает 1, если в символьном виде записан правильный IP-адрес, и 0 в противном случае -- для большинства системных вызовов и функций это нетипичная ситуация. Обратите внимание на использование указателя на структуру struct in_addr в качестве одного из параметров данной функции. Эта структура используется для хранения IP-адресов в сетевом порядке байт. То, что используется структура, состоящая из одной переменной, а не сама 32-битовая переменная, сложилось исторически, и авторы в этом не виноваты. Для обратного преобразования применяется функция inet_ntoa ().

Функции преобразования ІР-адресов Прототипы функций

#include <sys/socket.h> #include <arpa/inet.h> Mnclude <netinet/in.h> int inet_aton(const char *strptr,

struct in_addr *addrptr); char *inet_ntoa(struct in_addr *addrptr);

Описание функций

Функция inet_aton переводит символьный IP-адрес, расположенный по указателю strptr, в числовое представление в сетевом порядке байт и заносит его в структуру, расположенную по адресу addrptr. Функция возвращает значение 1, если в строке записан правильный IP-адрес, и значение 0 в противном случае. Структура типа struct in_addr используется для хранения ІР-адресов в сетевом порядке байт и выглядит так:

struct in_addr { in_addr_t s_addr;

};

To, что используется адрес такой структуры, а не просто адрес переменной типа in_addr_t, сложилось исторически.

Функция inet_ntoa применяется для обратного преобразования. Числовое представление адреса в сетевом порядке байт должно быть занесено в структуру типа struct in_addr, адрес которой addrptr передается функции как аргумент. Функция возвращает указатель на строку, содержащую символьное представление адреса. Эта строка располагается в статическом буфере, при последующих вызовах ее новое содержимое заменяет старое содержимое.

Функция Ьгего()

Функция Ьгего настолько проста, что про нее нечего рассказывать. Все видно из описания.

Функция bzero

Прототип функции

#include <string.h>

void bzero(void *addr, int n);

Описание функции

Функция bzero заполняет первые п байт, начиная с адреса addr, нулевыми значениями. Функция ничего не возвращает.

Теперь мы можем перейти к системным вызовам, образующим интерфейс между пользовательским уровнем стека протоколов TCP/IP и транспортным протоколом UDP.

Создание сокета. Системный вызов socket()

Для создания сокета в операционной системе служит системный вызов socket (). Для транспортных протоколов семейства TCP/IP существует два вида сокетов: UDP-сокет -- сокет для работы с датаграммами, и ТСР-сокет -- потоковый сокет. Однако понятие сокета (см. лекцию 14, раздел «Полные адреса. Понятие сокета (socket)») не ограничивается рамками только этого семейства протоколов. Рассматриваемый интерфейс сетевых системных вызовов (socket (), bind(), recvfrom(), send-to () ит. д.) в операционной системе UNIX может применяться и для других стеков протоколов (и для протоколов, лежащих ниже транспортного уровня).

При создании сокета необходимо точно специфицировать его тип. Эта спецификация производится с помощью трех параметров вызова socket (). Первый параметр указывает, к какому семейству протоколов относится создаваемый сокет, а второй и третий параметры определяют конкретный протокол внутри данного семейства.

Второй параметр служит для задания вида интерфейса работы с со-кетом -- будь это потоковый сокет, сокет для работы с датаграммами или какой-либо иной. Третий параметр указывает протокол для заданного типа интерфейса. В стеке протоколов TCP/IP существует только один протокол для потоковых сокетов -- TCP и только один протокол для датаграммных сокетов -- UDR, поэтому для транспортных протоколов TCP/IP третий параметр игнорируется.

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

Для транспортных протоколов TCP/IP мы всегда в качестве первого параметра будем указывать предопределенную константу AF_INET (Address family -- Internet) или ее синоним PF_INET (Protokol family -- Internet).

Второй параметр будет принимать предопределенные значения SOCK_STREAM для потоковых сокетов и SOCK_DGRAM -- для датаграммных.

Поскольку третий параметр в нашем случае не учитывается, в него мы будем подставлять значение 0.

Ссылка на информацию о созданном сокете помещается в таблицу открытых файлов процесса подобно тому, как это делалось для pip'oe и FIFO (см. семинар 5). Системный вызов возвращает пользователю файловый дескриптор, соответствующий заполненному элементу таблицы, который далее мы будем называть дескриптором сокета. Такой способ хранения информации о сокете позволяет, во-первых, процессам-детям наследовать ее от процессов-родителей, а во-вторых -- использовать для сокетов часть системных вызовов, которые уже знакомы нам по работе с pip'aMH и FIFO: close (), read {), write {).

Системный вызов для создания сокета Прототип системного вызова

tinclude <sys/types.h> ¦include <sys/socket.feint socket(int domain, int type, int protocol);

Описание системного вызова

Системный вызов socket служит для создания виртуального коммуникационного узла в операционной системе. Данное описание не является полным описанием системного вызова, а предназначено только для использования в нашем курсе. За полной информацией обращайтесь к UNIX Manual.

Параметр domain определяет семейство протоколов, в рамках которого будет осуществляться передача информации. Мы рассмотрим только два таких семейства из нескольких существующих. Для них имеются предопределенные значения параметра:

PF_l.NET - для семейства протоколов TCP/IP;

PFJJNIX - для семейства внутренних протоколов UNIX, иначе называемого еще UNIX domain.

Параметр type определяет семантику обмена информацией: будет ли осуществляться связь через сообщения (datagrams), с помощью установления виртуального соединения или еще каким-либо способом. Мы будем пользоваться только двумя способами обмена информацией с предопределенными значениями для параметра type:

SCCKJ5TREAM - для связи с помощью установления виртуального соединения;

SOCKJDGRAM - для обмена информацией через сообщения.

Параметр protocol специфицирует конкретный протокол для выбранного семейства протоколов и способа обмена информацией. Он имеет значение только в том случае, когда таких протоколов существует несколько. В нашем случае семейство протоколов и тип обмена информацией определяют протокол однозначно. Поэтому данный параметр мы будем полагать равным 0.

Возвращаемое значение

В случае успешного завершения системный вызов возвращает файловый дескриптор (значение большее или равное 0), который будет использоваться как ссылка на созданный коммуникационный узел при всех дальнейших сетевых вызовах. При возникновении какой-либо ошибки возвращается отрицательное значение.

Адреса сокетов. Настройка адреса сокета. Системный вызов bind()

Когда сокет создан, необходимо настроить его адрес. Для этого используется системный вызов bind(). Первый параметр вызова должен содержать дескриптор сокета, для которого производится настройка адреса. Второй и третий параметры задают этот адрес.

Во втором параметре должен быть указатель на структуру struct sockaddr, содержащую удаленную и локальные части полного адреса.

Указатели типа struct sockaddr* встречаются во многих сетевых системных вызовах; они используются для передачи информации о том, к какому адресу привязан или должен быть привязан сокет. Рассмотрим этот тип данных подробнее. Структура struct sockaddr описана в файле <sys/ socket .h> следующим образом:

struct sockaddr { short sa_family; char sa_data[14];

} ;

Такой состав структуры обусловлен тем, что сетевые системные вызовы могут применяться для различных семейств протоколов, которые по-разному определяют адресные пространства для удаленных и локальных адресов сокета. По сути дела, этот тип данных представляет собой лишь общий шаблон для передачи системным вызовам структур данных, специфических для каждого семейства протоколов. Общим элементом этих структур остается только поле short sa_f amily (которое в разных структурах, естественно, может иметь разные имена, важно лишь, чтобы все они были одного типа и были первыми элементами своих структур) для описания семейства протоколов. Системный вызов анализирует содержимое этого поля для точного определения состава поступившей информации.

Для работы с семейством протоколов TCP/IP мы будем использовать адрес сокета следующего вида, описанного в файле <netinet / in. h>:

struct sockaddr _in{

short sin_family; /* Избранное семейство протоколов

- всегда AF__INET */ unsigned short sin_port; /* 16-битовый номер-порта

в сетевом порядке байт */ struct in_addr sin_addr; /* Адрес сетевого

интерфейса */ char sin_zero [ 8 ] ; /* Это поле не используется,

но должно всегда быть заполнено нулями */

} ;

Первый элемент структуры -- sin_f amily -- задает семейство протоколов. В него мы будем заносить уже известную нам предопределенную константу AF_INET (см. предыдущий раздел).

Удаленная часть полного адреса -- IP-адрес -- содержится в структуре типа struct in_addr, с которой мы встречались в разделе «Функции преобразования IP-адресов inet_ntoa(), inet_aton()» .

Для указания номера порта предназначен элемент структуры sin_port, в котором номер порта должен храниться в сетевом порядке байт. Существует два варианта задания номера порта: фиксированный порт по желанию пользователя и порт, который произвольно назначает операционная система. Первый вариант требует указания в качестве номера порта положительного, заранее известного числа, и для протокола UDP обычно используется при настройке адресов сокетов и при передаче информации с помощью системного вызова sendto() (см. следующий раздел). Второй вариант требует указания в качестве номера порта значения 0. В этом случае операционная система сама привязывает сокет к свободному номеру порта. Этот способ обычно используется при настройке сокетов программ клиентов, когда заранее точно знать номер порта для программиста необязательно.

Какой номер порта может задействовать пользователь при фиксированной настройке? Номера портов с 1 по 1023 могут назначать сокетам только процессы, работающие с привилегиями системного администратора. Как правило, эти номера закреплены за системными сетевыми службами независимо от вида используемой операционной системы, для того чтобы пользовательские клиентские программы могли запрашивать обслуживание всегда по одним и тем же локальным адресам. Существует также ряд широко применяемых сетевых программ, которые запускают процессы с полномочиями обычных пользователей (например, Х-Windows). Для таких программ корпорацией Internet по присвоению имен и номеров (ICANN) выделяется диапазон адресов с 1024 по 49151, который нежелательно использовать во избежание возможных конфликтов. Номера портов с 49152 по 65535 предназначены для процессов обычных пользователей. Во всех наших примерах при фиксированном задании номера порта у сервера мы будем использовать номер 51000.

IP-адрес при настройке также может быть определен двумя способами. Он может быть привязан к конкретному сетевому интерфейсу (т. е. сетевой плате), заставляя операционную систему принимать/передавать информацию только через этот сетевой интерфейс, а может быть привязан и ко всей вычислительной системе в целом (информация может быть получена/отослана через любой сетевой интерфейс). В первом случае в качестве значения поля структуры sin_addr. s_addr используется числовое значение IP-адреса конкретного сетевого интерфейса в сетевом порядке байт. Во втором случае это значение должно быть равно значению предопределенной константы INADDR_ANY, приведенному к сетевому порядку байт.

Третий параметр системного вызова bind () должен содержать фактическую длину структуры, адрес которой передается в качестве второго параметра. Эта длина меняется в зависимости от семейства протоколов и даже различается в пределах одного семейства протоколов. Размер структуры, содержащей адрес сокета, для семейства протоколов TCP/IP может быть определен как sizeof(struct sockaddr_in).

Системный вызов для привязки сокета к конкретному адресу

Прототип системного вызова

#include <sys/types.h> #include <sys/socket.h>

int bind(int sockd, struct sockaddr *my_addr, int addrlen);

Описание системного вызова

Системный вызов bind служит для привязки созданного сокета к определенному полному адресу вычислительной сети.

Параметр sockd является дескриптором созданного ранее коммуникационного узла, т. е. значением, которое вернул системный вызов socket ().

Параметр my_addr представляет собой адрес структуры, содержащей информацию о том, куда именно мы хотим привязать наш сокет - то, что принято называть адресом сокета. Он имеет тип указателя на структуру-шаблон struct sockaddr, которая должна быть конкретизирована в зависимости от используемого семейства протоколов и заполнена перед вызовом.

Параметр addrlen должен содержать фактическую длину структуры, адрес которой передается в качестве второго параметра. Эта длина в разных семействах протоколов и даже в пределах одного семейства протоколов может быть различной (например, для UNIX Domain).

Возвращаемое значение

Системный вызов возвращает значение 0 при нормальном завершении и отрицательное значение - в случае ошибки.

Системные вызовы sendtoQ и recvfrom()

Для отправки датаграмм применяется системный вызов sendto (). В число параметров этого вызова входят:

дескриптор сокета, через который отсылается датаграмма;

адрес области памяти, где лежат данные, которые должны составить содержательную часть датаграммы, и их длина;

флаги, определяющие поведение системного вызова (в нашем случае они всегда будут иметь значение 0);

указатель на структуру, содержащую адрес сокета получателя, и ее фактическая длина.

Системный вызов возвращает отрицательное значение при возникновении ошибки и количество реально отосланных байт при нормальной работе. Нормальное завершение системного вызова не означает, что дата-грамма уже покинула ваш компьютер! Датаграмма сначала помещается в системный сетевой буфер, а ее реальная отправка может произойти после возврата из системного вызова. Вызов sendto () может блокироваться, если в сетевом буфере не хватает места для датаграммы.

Для чтения принятых датаграмм и определения адреса получателя (при необходимости) служит системный вызов recvf rom (). В число параметров этого вызова входят:

дескриптор сокета, через который принимается датаграмма;

адрес области памяти, куда следует положить данные, составляющие содержательную часть датаграммы;

максимальная длина, допустимая для датаграммы. Если количество данных датаграммы превышает заданную максимальную длину, то вызов по умолчанию рассматривает это как ошибочную ситуацию;

флаги, определяющие поведение системного вызова (в нашем случае они будут полагаться равными 0);

указатель на структуру, в которую при необходимости может быть занесен адрес сокета отправителя. Если этот адрес не требуется, то можно указать значение null;

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

Системные вызовы sendto и recvfrom

Прототипы системных вызовов

ftinclude <sys/types.h> ftinclude <sys/socket,h>

int sendto(int sockd, char *buff, int nbytes,

int flags, struct sockaddr *to, int addrlen); int recvfrom(int sockd, char *buff, int nbytes,

int flags, struct sockaddr *from, int *addrlen);

Описание системных вызовов

Системный вызов sendto предназначен для отправки датаграмм. Системный вызов recvfrom предназначен для чтения пришедших датаграмм и определения адреса отправителя. По умолчанию при отсутствии пришедших датаграмм вызов recvfrom блокируется до тех пор, пока не появится датаграмма. Вызов sendto может блокироваться при отсутствии места под датаграмму в сетевом буфере. Данное описание не является полным описанием системных вызовов, а предназначено только для использования в нашем курсе. За полной информацией обращайтесь к UNIX Manual.

Параметр sockd является дескриптором созданного ранее сокета, т. е. значением, возвращенным системным вызовом socket (), через который будет отсылаться или получаться информация.

Параметр buff представляет собой адрес области памяти, начиная с которого будет браться информация для передачи или размещаться принятая информация.

Параметр nbytes для системного вызова sendto определяет количество байт, которое должно быть передано, начиная с адреса памяти buff. Параметр nbytes для системного вызова recvf rom определяет максимальное количество байт, которое может быть размещено в приемном буфере, начиная с адреса buff.

Параметр to для системного вызова sendto определяет ссылку на структуру, содержащую адрес сокета получателя информации, которая должна быть заполнена перед вызовом. Если параметр from для системного вызова recvf rom не равен NULL, то для случая установления связи через пакеты данных он определяет ссылку на структуру, в которую будет занесен адрес сокета отправителя информации после завершения вызова. В этом случае перед вызовом эту структуру необходимо обнулить.

Параметр addrlen для системного вызова sendto должен содержать фактическую длину структуры, адрес которой передается в качестве параметра to. Для системного вызова recvf rom параметр addrlen является ссылкой на переменную, в которую будет занесена фактическая длина структуры адреса сокета отправителя, если это определено параметром from. Заметим, что перед вызовом этот параметр должен указывать на переменную, содержащую максимально допустимое значение такой длины. Если параметр from имеет значение NULL, то и параметр addrlen может иметь значение NULL.

Параметр flags определяет режимы использования системных вызовов. Рассматривать его применение мы в данном курсе не будем, и поэтому берем значение этого параметра равным 0.

Возвращаемое значение

В случае успешного завершения системный вызов возвращает количество реально отосланных или принятых байт. При возникновении какой-либо ошибки возвращается отрицательное значение.

Определение IP-адресов для вычислительного комплекса

Для определения IP-адресов на компьютере можно воспользоваться утилитой /sbin/ifconf ig. Эта утилита выдает всю информацию о сетевых интерфейсах, сконфигурированных в вычислительной системе. Пример выдачи утилиты показан ниже:

ethO Link encap:Ethernet HWaddr 00:90:27:A7:IB:FE

inet addr:192.168.253.12 Beast:192.168.253.255 Mask:255.255.255.0

UP BROADCAST NOTRAILERS RUNNING MULTICAST MTU:1500 Metric:l RX packets:122556059 errors:0 dropped:0 overruns:0 frame:0 TX packets:116085111 errors:0 dropped:0 overruns:0 carrier:0 collisions:0

txqueuelen:100 RX bytes:2240402748 (2136.6 Mb) TX bytes:3057496950 (2915.8 Mb) Interrupt:10 Base address:0x1000 lo Link encap:Local Loopback

inet addr:127.0.0.1 Mask:255.0.0.0

UP LOOPBACK RUNNING MTU:16436 Metric:1

RX packets:403 errors:0 dropped:0 overruns:0 frame:0

TX packets:403 errors:0 dropped:0 overruns:0

carrier:0 collisions:0 txqueuelen:0

RX bytes:39932 (38.9 Kb) TX bytes:39932 (38.9 Kb)

Сетевой интерфейс ethO использует протокол Ethernet. Физический 48-битовый адрес, зашитый в сетевой карте, -- 00:90:27: А7:1В: FE. Его IP-адрес - 192.168.253.12.

Сетевой интерфейс 1о не относится ни к какой сетевой карте. Это так называемый локальный интерфейс, который через общую память эмулирует работу сетевой карты для взаимодействия процессов, находящихся на одной машине по полным сетевым адресам. Наличие этого интерфейса позволяет отлаживать сетевые программы на машинах, не имеющих сетевых карт. Его IP-адрес обычно одинаков на всех компьютерах -127 .0.0.1.

Пример программы UDP-клиента

Рассмотрим, наконец, простой пример программы 14--15-l.c. Эта программа является UDP-клиентом для стандартного системного сервиса echo. Стандартный сервис принимает от клиента текстовую датаграмму и, не изменяя ее, отправляет обратно. За сервисом зарезервирован номер порта 7. Для правильного запуска программы необходимо указать символьный IP-адрес сетевого интерфейса компьютера, к сервису которого нужно обратиться, в качестве аргумента командной строки, например:

a.out 192.168.253.12 Ниже следует текст программы:

/* Простой пример UDP клиента для сервиса echo */

#include <sys/types.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

#include <string.h>

#include <stdio.h>

#include <errno.h>

#include <unistd.h>

int main(int argc, char **argv)

{

int sockfd; /* Дескриптор сокета */

int n, len; /* Переменные для различных длин и

количества символов */ char sendline[1000], recvline[1000]; /* Массивы

для отсылаемой и принятой строки */ struct sockaddr_in servaddr, cliaddr; /* Структуры для адресов сервера и клиента */

/* Сначала проверяем наличие второго аргумента в командной строке. При его отсутствии ругаемся и прекращаем работу */ if(argc '= 2) {

printf("Usage: a.out <IP address>\n");

exit(1);

}

/* Создаем UDP сокет */

if((sockfd = socket(PF_INET, SOCK_DGRAM, 0)) < 0){ perror(NULL); /* Печатаем сообщение об ошибке */ exit (1) ;

}

/* Заполняем структуру для адреса клиента: семейство протоколов TCP/IP, сетевой интерфейс - любой, номер порта по усмотрению операционной системы. Поскольку в структуре содержится дополнительное не нужное нам поле, которое должно быть нулевым, перед заполнением обнуляем ее всю */

bzero(kcliaddr, sizeof(cliaddr)); cliaddr.sin_family = AF_INET; cliaddr . sin__port = htons(O);

cliaddr.sin_addr.s_addr = htonl(INADDR_ANY); /* Настраиваем адрес сокета */ if(bind(sockfd, (struct sockaddr *) kcliaddr, sizeof(cliaddr)) < 0){ perror(NULL);

close(sockfd); /* По окончании работы закрываем дескриптор сокета */ exit(1) ;

/* Заполняем структуру для адреса сервера: семейство протоколов TCP/IP, сетевой интерфейс -из аргумента командной строки, номер порта 7. Поскольку в структуре содержится дополнительное не нужное нам поле, которое должно быть нулевым, перед заполнением обнуляем ее всю */ bzero(kservaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(7);

if(inet_aton(argv[1], kservaddr.sin_addr) == 0){ printf("Invalid IP address\n"); close(sockfd); /* По окончании работы закрываем

дескриптор сокета */ exit(1);

}

/* Вводим строку, которую отошлем серверу */

printf("String => ");

fgets(sendline, 1000, stdin);

/* Отсылаем датаграмму */

іf(sendto(sockfd, sendline, strlen(sendline)+1,

0, (struct sockaddr *) kservaddr,

sizeof(servaddr)) < 0){

perror(NULL);

close(sockfd);

exit(1) ;

}

/* Ожидаем ответа и читаем его. Максимальная допустимая длина датаграммы - 100 0 символов, адрес отправителя нам не нужен */ if((n = recvfrom(sockfd, recvline, 1000, 0, (struct sockaddr *) NULL, NULL)) < 0){

perror(NULL);

close(sockfd);

exit(1) ;

}

/* Печатаем пришедший ответ и закрываем сокет */ printf("%s\n", recvline); close(sockfd); return 0;

Наберите и откомпилируйте программу. Перед запуском «узнайте у своего системного администратора», запущен ли в системе стандартный UDP-сервис echo и если нет, попросите стартовать его. Запустите программу с запросом к сервису своего компьютера, к сервисам других компьютеров. Если в качестве IP-адреса указать несуществующий адрес, адрес выключенной машины или машины, на которой не работает сервис echo, то программа бесконечно блокируется в вызове recvfrom(), ожидая ответа. Протокол UDP не является надежным протоколом. Если датаграмму доставить по назначению не удалось, то отправитель никогда об этом не узнает!

Пример программы UDP-сервера

Поскольку UDP-сервер использует те же самые системные вызовы, что и UDP-клиент, мы можем сразу приступить к рассмотрению примера UDP-сервера (программа 15--16-2.с) для сервиса echo.

/* Простой пример UDP-сервера для сервиса echo */

ttinclude <sys/types.h>

ttinclude <sys/socket.h>

ttinclude <netinet/in.h>

#include <arpa/inet.h>

#include <string.h>

#include <stdio.h>

#include <errno.h>

ttinclude <unistd.h>

int main()

{

int sockfd; /* Дескриптор сокета */

int clilen, n; /* Переменные для различных длин

и количества символов */ char line[1000]; /* Массив для принятой и

отсылаемой строки */ struct sockaddr_in servaddr, cliaddr; /* Структуры

для адресов сервера и клиента */ /* Заполняем структуру для адреса сервера: семейство протоколов TCP/IP, сетевой интерфейс - любой, номер порта 51000. Поскольку в структуре содержится дополнительное не нужное нам поле, которое должно быть нулевым, перед заполнением обнуляем ее всю */ bzero(&servaddr, s izeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(51000);

servaddr.sin_addr.s_addr = htonl(INADDR_ANY); /* Создаем UDP-сокет */

if((sockfd = socket(PF_INET, SOCK_DGRAM, 0)) < 0){ perror(NULL); /* Печатаем сообщение об ошибке */ exit(1) ;

}

/* Настраиваем адрес сокета */

if(bind(sockfd, (struct sockaddr *) &servaddr,sizeof(servaddr)) < 0){ ;

perror(NULL);

close(sockfd);

exit(1) ;

}

while(1) {

/* Основной цикл обслуживания*/ /* В переменную clilen заносим максимальную длину для ожидаемого адреса клиента */ clilen = sizeof(cliaddr);

/* Ожидаем прихода запроса от клиента и читаем его. Максимальная допустимая длина датаграммы -999 символов, адрес отправителя помещаем в структуру cliaddr, его реальная длина будет занесена в переменную clilen */ if((n = recvfrom(sockfd, line, 999, 0, (struct sockaddr *) &cliaddr, &clilen)) < 0){

perror(NULL);

close(sockfd) ;

exit(1) ;

}

/* Печатаем принятый текст на экране */ printf("%s\n", line);

/* Принятый текст отправляем обратно по адресу отправителя */

if(sendtо(sockfd, line, strlen(line), 0, (struct sockaddr *) &cliaddr, clilen) < 0){

perror(NULL);

close(sockfd);

exit (1) ;

) /* Уходим ожидать новую датаграмму*/

}

return 0;

Наберите и откомпилируйте программу. Запустите ее на выполнение. Модифицируйте текст программы UDP-клиента (программа 14--15-1.с), заменив номер порта с 7 на 51000. Запустите клиента с другого виртуального терминала или с другого компьютера и убедитесь, что клиент и сервер взаимодействуют корректно.

Организация связи между процессами с помощью установки логического соединения

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

Какие действия должен выполнить клиент для того, чтобы связаться по телефону с сервером? Во-первых, необходимо приобрести телефон (создать сокет), во-вторых, подключить его на АТС -- получить номер (настроить адрес сокета). Далее требуется позвонить серверу (установить логическое соединение). После установления соединения можно неоднократно обмениваться с сервером информацией (писать и читать из потока данных). По окончании взаимодействия нужно повесить трубку (закрыть сокет).

Первые действия сервера аналогичны действиям клиента. Он должен приобрести телефон и подключить его на АТС (создать сокет и настроить его адрес). А вот дальше поведение клиента и сервера различно. Представьте себе, что телефоны изначально продаются с выключенным звонком. Звонить по ним можно, а вот принять звонок -- нет. Для того чтобы вы могли пообщаться, необходимо включить звонок. В терминах сокетов это означает, что ТСР-сокет по умолчанию создается в активном состоянии и предназначен не для приема, а для установления соединения. Для того чтобы соединение принять, сокет требуется перевести в пассивное состояние.

Если два человека беседуют по телефону, то попытка других людей дозвониться до них окажется неудачной. Будет идти сигнал «занято», и соединение не установится. В то же время хотелось бы, чтобы клиент в такой ситуации не получал отказ в обслуживании, а ожидал своей очереди. Подобное наблюдается в различных телефонных справочных, когда вы слышите «Ждите, пожалуйста, ответа. Вам обязательно ответит оператор». Поэтому следующее действие сервера -- это создание очереди для обслуживания клиентов. Далее сервер должен дождаться установления соединения, прочитать информацию, переданную по линии связи, обработать ее и отправить полученный результат обратно. Обмен информацией может осуществляться неоднократно. Заметим, что со кет, находящийся в пассивном состоянии, не предназначен для операций приема и передачи информации. Для общения на сервере во время установления соединения автоматически создается новый потоковый сокет, через который и производится обмен данными с клиентами. По окончании общения сервер «кладет трубку» (закрывает этот новый сокет) и отправляется ждать очередного звонка.

Схематично эти действия выглядят так, как показано на рисунке 14-15.7. Как и в случае протокола UDP отдельным действиям или их группам соответствуют системные вызовы, частично совпадающие с вызовами для протокола UDP. Их названия написаны справа от блоков соответствующих действий.

Для протокола TCP неравноправность процессов клиента и сервера видна особенно отчетливо в различии используемых системных вызовов. Для создания сокетов и там, и там по-прежнему используется системный вызов socket (). Затем наборы системных вызовов становятся различными.

Для привязки сервера к IP-адресу и номеру порта, как и в случае UDP-протокола, используется системный вызов bind(). Для процесса клиента эта привязка объединена с процессом установления соединения с сервером в новом системном вызове connect () и скрыта от глаз пользователя. Внутри этого вызова операционная система осуществляет настройку сокета на выбранный ею порт и на адрес любого сетевого интерфейса. Для перевода сокета на сервере в пассивное состояние и для создания очереди соединений служит системный вызов listen(). Сервер ожидает соединения и получает информацию об адресе соединившегося с ним клиента с помощью системного вызова accept (). Поскольку установленное логическое соединение выглядит со стороны процессов как канал связи, позволяющий обмениваться данными с помощью потоковой модели, для передачи и чтения информации оба системных вызова используют уже известные нам системные вызовы read () и write (), а для завершения соединения -- системный вызов close (). Необходимо отметить, что при работе с сокетами вызовы read () и write () обладают теми же особенностями поведения, что и при работе с р1р'ами и FIFO (см. семинар 5).

Сервер

Клиент

Создание сокета

socket()

Создание сокета

socket()

--

Ожидание желающих установить соединение. Установка соединения. Создание нового потокового сокета

¦

г*-

Ожидание получения

информации. Чтение информации

Обработка информаии

Отправление ответа

Окончание соединения

connect()

Передача информации серверу

write()

і

Ожидание получения информации. Чтение информации

read()

Окончание соединения

Рис. 14-15.7. Схема взаимодействия клиента и сервера для протокола TCP

Установление логического соединения. Системный вызов connectQ

Среди системных вызовов со стороны клиента появляется только один новый -- connect (). Системный вызов connect () при работе с ТСР-сокетами служит для установления логического соединения со стороны клиента. Вызов connect () скрывает внутри себя настройку сокета на выбранный системой порт и произвольный сетевой интерфейс (по сути дела, вызов bind() с нулевым номером порта и IP-адресом INADDR_ANY). Вызов блокируется до тех пор, пока не будет установлено логическое соединение, или пока не пройдет определенный промежуток времени, который может регулироваться системным администратором.

Для установления соединения необходимо задать три параметра: дескриптор активного сокета, через который будет устанавливаться соединение, полный адрес сокета сервера и его длину.

Системный вызов connect()

Прототип системного вызова

Mnclude <sys/types.h> Mnclude <sys/socket.h>

int connect(int sockd, struct sockaddr *servaddr, int addrlen);

Описание системного вызова

Системный вызов connect служит для организации связи клиента с сервером. Чаще всего он используется для установления логического соединения, хотя может быть применен и при связи с помощью датаграмм (connectionless). Данное описание не является полным описанием системного вызова, а предназначено только для использования в нашем курсе. Полную информацию можно найти в UNIX Manual.

Параметр sockd является дескриптором созданного ранее коммуникационного узла, т. е. значением, которое вернул системный вызов socket ().

Параметр servaddr представляет собой адрес структуры, содержащей информацию о полном адресе сокета сервера. Он имеет тип указателя на структуру-шаблон struct sockaddr, которая должна быть конкретизирована в зависимости от используемого семейства протоколов и заполнена перед вызовом.

Параметр addrlen должен содержать фактическую длину структуры, адрес которой передается в качестве второго параметра. Эта длина меняется в зависмости от семейства протоколов и различается даже в пределах одного семейства протоколов (например, для UNIX Domain).

При установлении виртуального соединения системный вызов не возвращается до его установления или до истечения установленного в системе времени - timeout. При использовании его в connectionless связи вызов возвращается немедленно.

Возвращаемое значение

Системный вызов возвращает значение 0 при нормальном завершении и отрицательное значение, если в процессе его выполнения возникла ошибка.

Пример программы TCP-клиента

Рассмотрим пример -- программу 14--15-З.с. Это простой ТСР-кли-ент, обращающийся к стандартному системному сервису echo. Стандартный сервис принимает от клиента текстовую датаграмму и, не изменяя ее, отправляет обратно. За сервисом зарезервирован номер порта 7. Заметим, что это порт 7 TCP -- не путать с портом 7 UDP из примера в разделе «Пример программы UDP-клиента»! Для правильного запуска программы необходимо указать символьный IP-адрес сетевого интерфейса компьютера, к сервису которого требуется обратиться, в качестве аргумента командной строки, например:

a.out 192.168.253.12

Для того чтобы подчеркнуть, что после установления логического соединения клиент и сервер могут обмениваться информацией неоднократно, клиент трижды запрашивает текст с экрана, отсылает его серверу и печатает полученный ответ. Ниже представлен текст программы.

/* Простой пример TCP-клиента для сервиса echo */

#include <sys/types.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

#include <string.h>

#include <stdio.h>

#include <errno.h>

#include <unistd.h>

void main(int argc, char **argv)

{

int sockfd; /* Дескриптор сокета */

int n; /* Количество переданных или прочитанных

символов */ int i; /* Счетчик цикла */

char sendline[1000],recvline[1000] ; /* Массивы для отсылаемой и принятой строки */ struct sockaddr_in servaddr; /* Структура для

адреса сервера */ /* Сначала проверяем наличие второго аргумента в командной строке. При его отсутствии прекращаем работу */ if(argc != 2){

printf("Usage: a.out <IP address>\n");

exit(l);

}

/* Обнуляем символьные массивы */ bzero(sendline,1000); bzero(recvline,1000); /* Создаем ТСР-сокет */

if((sockfd = socket(PF_INET, SOCK_STREAM, 0)) < 0){ perror(NULL); /* Печатаем сообщение об ошибке */

exit (1) ;

}

/* Заполняем структуру для адреса сервера: семейство протоколов TCP/IP, сетевой интерфейс - из аргумента командной строки, номер порта 7. Поскольку в структуре содержится дополнительное не нужное нам поле, хоторое должно быть нулевым, перед заполнением обнуляем ее всю */ bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(51000);

іf(inet_aton(argv[1], kservaddr.sin_addr) == 0){ printf("Invalid IP address\n"); close(sockfd); exit(1);


Подобные документы

  • Основные понятия об операционных системах. Виды современных операционных систем. История развития операционных систем семейства Windows. Характеристики операционных систем семейства Windows. Новые функциональные возможности операционной системы Windows 7.

    курсовая работа [60,1 K], добавлен 18.02.2012

  • Понятие виртуальной памяти, ее реализация. Особенности страничной организации по требованию. Этапы обработки ситуации отсутствия страницы в памяти. Стратегии (алгоритмы) замещения страниц. Особенности некоторых операционных систем: Windows NT и Solaris.

    презентация [2,2 M], добавлен 24.01.2014

  • Изучение особенностей операционной системы, набора программ, контролирующих работу прикладных программ и системных приложений. Описания архитектуры и программного обеспечения современных операционных систем. Достоинства языка программирования Ассемблер.

    презентация [1,3 M], добавлен 22.04.2014

  • Общая характеристика преимуществ взаимодействующих процессов: модульность, ускорение вычислений. Знакомство с основами современных операционных систем. Анализ особенностей использования общего почтового ящика, рассмотрение способов создания и удаления.

    презентация [1,6 M], добавлен 24.01.2014

  • Характеристика сущности, назначения, функций операционных систем. Отличительные черты их эволюции. Особенности алгоритмов управления ресурсами. Современные концепции и технологии проектирования операционных систем, требования, предъявляемые к ОС XXI века.

    курсовая работа [36,4 K], добавлен 08.01.2011

  • Операционная система - программа, которая загружается при включении компьютера. Способы реализации интерфейса и классификация операционных систем. Организация файловой системы, типы файлов и их наименования. Понятие каталога, атрибуты файловой системы.

    реферат [16,6 K], добавлен 25.02.2011

  • Важность операционной системы для мобильных устройств. Популярность операционных систем. Доля LINUX на рынке операционных систем. История OS Symbian, BlackBerry OS, Palm OS. Отличия смартфона от обычного мобильного телефона. Учет ограничений по памяти.

    презентация [477,3 K], добавлен 01.12.2015

  • Основные понятия операционных систем. Современное оборудование компьютера. Преимущества и недостатки операционной системы Linux. Функциональные возможности операционной системы Knoppix. Сравнительная характеристика операционных систем Linux и Knoppix.

    реферат [1,5 M], добавлен 17.12.2014

  • Основные понятия операционных систем. Синхронизация и критические области. Сигналы и взаимодействие между процессами. Управление памятью. Драйверы устройств. Особенности современных операционных систем. Центральный процессор, микросхемы часов и таймеров.

    учебное пособие [1,2 M], добавлен 24.01.2014

  • Использование операционных систем Microsoft Windows. Разработка операционной системы Windows 1.0. Возможности и характеристика последующих версий. Выпуск пользовательских операционных систем компании, доработки и нововведения, версии Windows XP и Vista.

    реферат [23,3 K], добавлен 10.01.2012

Работы в архивах красиво оформлены согласно требованиям ВУЗов и содержат рисунки, диаграммы, формулы и т.д.
PPT, PPTX и PDF-файлы представлены только в архивах.
Рекомендуем скачать работу.