ГЛАВА 1 КРАТКИЕ СВЕДЕНИЯ О СИСТЕМАХ UNIX И TCP/IP

1.1. ВВЕДЕНИЕ

Минувшее десятилетие характеризовалось быстрым развитием персональной вычислительной техники, рождением мира рабочих станций и бурным развитием сетей, позволяющих осуществлять обмен информацией между компьютерами. ОС UNIX появилась как операционная система, используемая на рабочих станциях. Кроме того, эта система может использоваться практически на всех компьютерах, от микро-компьютеров типа PC (Personal Computer) и Macintosh до супер-ЭВМ семейств Cray и IBM. Успех UNIX повлек за собой успех TCP/IP в качестве протокола сетевого обмена. Несмотря на постепенное внедрение стандартизованных протоколов OSI, протокол TCP/IP все еще достаточно широко применяется, в том числе и при работе в отличных от UNIX операционных системах. После краткой исторической справки в данной главе изложены основные понятия системы UNIX и протоколов TCP/IP, необходимые для правильного понимания проблемы.

1.2. СИСТЕМЫ UNIX И TCP/IP

Сведения из истории UNIX

Системы UNIX были разработаны на основе двух источников: UNIX System V и BSD (Berkeley Software Distribution). В 1983 году появилась первая версия UNIX System V, разработанная в лабораториях AT&T. В настоящее время наиболее распространенными ее версиями являются System V Release 3.2 (появилась в 1988 г.) и System V Release 4.0 (начало 1990 г.). Первая версия BSD была разработана в 1986 году в лабораториях Университета Беркли, штат Калифорния. В настоящее время используется версия BSD 4.3 (распространяется с 1988 г.). Версия 4.4 находится в стадии разработки. Большинство разработчиков использовали в качестве исходной системы System V (Hewlett Packard, Bull, IBM), добавив то, что называют расширениями BSD. Разработчики систем UNIX для микро- компьютеров, в частности, SCO (Santa Cruz Operation) и Interactive, также предлагают системы UNIX на базе System V. Операционная система SunOS фирмы Sun, в свою очередь, также является производной от BSD с расширениями System V. Однако, фирма Sun объявила о своем стремлении перейти к SVR4 (System V Release 4).

В 1988 году большинство разработчиков объединились в рамках фонда OSF (Open Software Foundation), с целью создания новой, независимой от AT&T, системы UNIX. Фирмы AT&T и Sun объединились в рамках UI (UNIX International) для того, чтобы содействовать продвижению SVR4 на компьютерный рынок. AT&T создала свой филиал USL (UNIX System Laboratories), в задачи которого входит разработка и распространение систем UNIX. Органы, занимающиеся стандартизацией систем UNIX:
* IEEE (Институт инженеров по электротехнике и радиоэлектронике) определяет группу стандартов POSIX;
* X/Open - международная группа поставщиков UNIX, занимающаяся проблемами мобильности.

TCP/IP

TCP/IP является плодом проекта DAPRA (Управление перспективных исследований и разработок) Министерства обороны США. Созданный в 1980 году, он был затем интегрирован в версию BSD 4.2. Впоследствии были разработаны версии TCP/IP для System V,права на которые приобрела фирма AT&T. В настоящее время TCP/IP входит в число основных сервисных программ систем UNIX, независимо от машин на которых он применяется.

1.3. ОСНОВНЫЕ ЭЛЕМЕНТЫ СИСТЕМЫ UNIX

В данном разделе нами дается определение основных элементов системы UNIX.

Ядро системы UNIX

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

Программа

Программа UNIX представляет собой исполняемый файл (с инструкциями).

Процесс

Процесс является активным элементом, управляемым системой UNIX: программа плюс контекст. Процесс вводится в виде элемента в таблицу (usr/include/sys/proc.h), связанную с определенной структурой (U-structure), определяющей все ресурсы, используемые процессом.

Обращение к ОС и вызов библиотеки

Ядро UNIX располагает определенным количеством точек входа, с помощью которых процесс может непосредственно задействовать сервисные программы ядра: эти точки входа называются системными вызовами или обращениями к системе). Эти обращения осуществляются на языке Си в форме функций, имеющихся в библиотеке UNIX. Доступ к отдельным сервисным программам обеспечивает не библиотека UNIX, а их собственные библиотеки. Стандартная библиотека Си, независимая от UNIX, играет особую роль: она позволяет осуществлять ввод-вывод с буферизацией (buffer). Например, записанные данные хранятся в буфере до его заполнения, либо до перехода к операции считывания.

Создание и разрушение процессов

Единственным способом создания процесса в системе UNIX является использование системного вызова fork (). Функционально, новый или порожденный процесс создается посредством копирования выполняемого или порождающего процесса. Порожденный процесс наследует большую часть атрибутов своего родителя, и в частности, открытые файлы (порожденный процесс обладает копией дескрипторов файлов, открытых его родителем). Особый процесс init является общим предком по отношению ко всем остальным. Он инициализирует совокупность администраторов системы (демонов) и связывает процесс с каждым терминалом. Системный вызов exec () позволяет запустить выполнение новой программы в рамках текущего процесса. Порождающий процесс может быть синхронизирован с порожденными благодаря функции wait (). Последние сигнализируют о своем окончании посредством обращения exit ().
Если порожденный процесс заканчивается раньше своего родителя, обработка данных производится следующим образом:
- если процесс-родитель находился в состоянии ожидания, обеспеченного функцией wait (), он выходит из этого состояния;
- если родителем не была задействована функция типа wait (), порожденный процесс остается в состоянии "зомби", т.е. он возвращает все ресурсы, приданные ему системой, но сохраняет свое местонахождение в таблице процессов до подачи сигнала о его окончании.
Если порождающий процесс закончился раньше своих преемников, приемным родителем последних становится процесс init ().

ПРОГРАММА 2
/*Пpимеp pаботы функции fork()*/
 #include <stdio.h>
 main()
 {
 /*создание (fork) пpоцесса */
    switch (fork())  {
    case 0 :
 /*поpожденный пpоцесс */
       printf(" fils pid %d ppid %d \n", getpid(), getppid());
       exit(0);
    case -1 :
 /*ошибка*/
       exit(1);
 default :
 /*поpождающий пpоцесс */
       printf(" pere pid %d ppid %d \n", getpid(), getppid());
 /*поpождающий пpоцесс ждет, пока не закончится поpожденный */
       wait() ;
       exit(0);
   }
 }

Идентификация процесса (PID)

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

Идентификация порождающего процесса (PPID) Как мы видели выше, каждый процесс порождается каким-либо другим процессом, который сам, в свою очередь, обладает PID; им является PPID (Parent Process ID). Его получают посредством системного вызова getppid ().

Идентификация группы процессов (PGID)

Каждый процесс входит в состав группы, идентифицируемой своим номером PGID (Process Group ID). Внутри каждой группы существует особый процесс, называемый лидером группы: это процесс, PID которого равен PGID. Понятие группы процессов вводится для приемки сигнала (см. раздел "Сигналы"). Номер группы можно получить посредством вызова getprgp (). Изменить его можно с помощью функции setprgp ().

Идентификация группы терминалов и операторский терминал

Каждый процесс является, кроме того, членом другой группы, Terminal Group, номером которой является PID лидера группы процессов, открывшего терминал, называемый операторским терминалом. Данное понятие используется для сигналов, посылаемых с этого терминала. Любой процесс может использовать связанный с ним операторский терминал, открыв файл /dev/tty. Когда пользователь выполняет на своем терминале команду с помощью командного интерпретатора shell, эта команда вызывает запуск нового процесса. Этот процесс является лидером группы для всей создаваемой им цепочки процессов. Сигнал прерывания, посылаемый с клавиатуры этого терминала, посылается всей совокупности данных процессов. Любой процесс может стать лидером группы с помощью вызова setprgp () и, таким образом , избежать привязки к терминалу и, следовательно, возможности быть прерванным. В системах BSD он должен, кроме того, явным образом отсоединиться от операторского терминала посредством опции TIOCNOTTY вызова ioctl (), использованного для /dev/tty.

Идентификация пользователя (UID) и идентификация группы (GID)

В системах UNIX каждому пользователю присваивается номер UID (User ID), определяемый в файле /etc/passwd. Пользователь входит в группу пользователей, идентифицируемую посредством GID (Group ID). Пара (UID, GID) определяет право доступа пользователя к ресурсам системы (файлам). Для получения этих номеров (UID,GID) используют вызовы getuid () и getgid (). Единственный пользователь обладает всеми правами: это привилегированный пользователь, которому присвоен нулевой номер. Есть еще два идентификатора: EUID (Effective User ID) и EGID (Effective Group ID). При работе процесса с файлом они могут изменить UID и GID, связанные с процессом, если установить бит SUID (Set User ID) или SGID (Set Group ID) этого файла. Процесс принимает UID владельца файла. Этот способ представляет особую ценность, если необходимо, чтобы пользователь мог выполнить программу, принадлежащую привилегированному пользователю: достаточно установить бит SUID в соответствующем файле. EUID и EGID можно получить с помощью системных вызовов geteuid () и getegid ().

Командный интерпретатор shell

Командный интерпретатор shell системы UNIX представляет собой программу, обеспечивающую сопряжение между пользователем и системой. Это одновременно и интерпретатор, и командный язык. Наиболее распространенными интерпретаторами shell являются: * Bourne shell: /bin/sh; * Korn shell: /bin/ksh; * C shell: /bin/csh. Будучи более мощными, чем Bourne shell, оболочки C shell и Korn shell становятся все более и более распространенными. Последовательность команд shell можно сохранить в файле, который в этом случае носит название командного файла (script).

Имена файлов и полные имена (pathnames)

Каждый файл или каталог в системе UNIX обладает одним или несколькими именами, т.е. он обозначается в данном каталоге строкой ASCII-символов, заканчивающейся нулевым символом (`\0 `). Например: libc.a. Pathname (полное имя файла) представляет собой строку символов, созданную на основе имен файлов и определяющую местона- хождение файла в иерархической системе. Каждое имя отделяется символом `/`. Например: /usr/lib/libc.a. Для удобства, имя файла и полное имя часто смешивают.

Ввод-вывод

Для осуществления ввода-вывода в системах UNIX существуют два способа: - использование стандартной библиотеки Си: функции fopen (), fread (), fwrite ()... Примитив fopen () возвращает указатель объекта типа FILE (FILE pointer). Запись и считыва- ние, т.о. осуществляются в буферном режиме. - использование библиотеки UNIX: системные вызовы open (), read (), write ()... Примитив open () возвращает целое значение, называемое file descriptor (дескриптор файла). Shell присваивает номер 0 стандартному вводу, 1 - стандартному выводу и 2 - сообщению об ошибке (stderr). Последующие номера присваиваются по нарастающей, за исключением случаев, когда номер был освобожден процессом посредством вызова функции close () или exit (). Другие примитивы возвращают дескриптор файла: creat (), dup (), pipe (), socket () и fcntl (). Функция fdopen () устанавливает связь между стандартной библиотекой Си и библиотекой UNIX, связывая FILE pointer c file descriptor. Системные вызовы fcntl () и ioctl () позволяют изменять характеристики уже открытого файла: право доступа, блокировка считывания или записи. Ввод-вывод на диск осуществляется механизмом кэширования с предварительным считыванием и отсроченной записью. Кэшем называется участок памяти, отведенный под эти операции. Т.о., при записи, данные передаются непосредственно на диск только тогда, когда кэш переполнен или существует потребность в записи именно на диск (sync оператора или демона update). Вызов dup () позволяет присваивать файлу дополнительный file descriptor, а система гарантирует, что этот новый номер является наименьшим из имеющихся. Это позволяет переориентировать стандартный ввод-вывод и вывод сообщений об ошибках.

ПРОГРАММА 3
/*пpимеp пеpеиндексации вывода в файл */
 #include <stdio.h>
 #include <fcntl.h>


 main()
 {
    int fd; /* дескpиптоp файла */
 /* откpытие вpеменного файла с маской создания 666*/
    fd = open ("/tmp/file", O_CREAT|O_RDWR, 0666);
 /*можно было бы сделать и пpоще : с помощью функции dup2,котоpая эквивалентна
   close()+dup() : dup2(1,fd),
   dup2(2,fd) */
    close(1);
    close(2);
    dup(fd);
    dup(fd);
 /*запись идет в stdout и stderr; из-за пеpеиндексации
   запись идет в файл /tmp/file */
    write(1, "ecriture stdout\n", 16);
    write(2, "ecriture stderr\n", 16);
 }

Сигналы

Сигнал представляет собой программное прерывание, посланное процессу, для того, чтобы сообщить о каком-либо событии, ожидаемом или нет. Источник сигнала не может знать отношение к этому получателя, особенно если последний решил игнорировать получение сигнала: источник об этом просто ничего не узнает! Сигналы могут быть посланы:
- одним процессом другому: в этом случае используется системный вызов kill ();
- ядром процессу (для того, чтобы указать, например, на фатальную ошибку, вызывающую разрушение процесса).
Параметры обработки, принятые по умолчанию при получении сигнала, можно изменить с помощью специального handler (обра- ботчика). Handler связывается с сигналом посредством вызова signal (). Точно также, сигналы можно игнорировать, используя:

signal (NOMSIGNAL, SIG_IGN);

Вызов kill () позволяет послать сигнал процессу (по его PID) или группе процессов (по ее PID).

ПРОГРАММА 4
/*Пpимеp упpавления сигналом SIGINT*/
 #include <signal.h>


 /*обpаботка связанная с сигналом пpеpывания
  SIGINT */
 tint()
 {
 /*вывод на экpан и выход         */
    printf("signal d'interruption recu\n");
    exit(1);
 }

 main()
 {
 /*устанавливается обpаботчик сигнала SIGINT */       signal(SIGINT, tint);
 /*ожидание возможного пpеpывания */
    sleep(100);
    exit(0);
 }

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

ПРОГРАММА 5

/*Пpимеp блокиpования сигнала*/
 #include <stdio.h>
 /*глобальная пеpеменная, значение котоpой меняется пpи
   появлении SIGINT */
 int flag = 0;

 /*обpаботчик SIGINT    */
 tint()
 {
 /*вывод сообщения и установка флага */
         printf("signal d'interruption recu\n");
         flag = 1;
 }


 main()
 {
 /*блокиpовка SIGQUIT и SIGINT пpи входе в кpитическую
   область */
 #ifdef BSD
 /*обpаботчик BSD       */
        int oldmask;
        oldmask = sigblock(sigmask(SIGQUIT)|sigmask(SIGINT));
 /*кpитическая область  */
        ..............
        sigsetmask(oldmask);
 #endif

 #ifdef SYS5
 /*обpаботчик SYSTEM V  */
        sighold(SIGQUIT);
        sighold(SIGINT);
 /*кpитическая область  */
        ..............
        sigrelse(SIGQUIT);
        sigrelse(SIGINT);
 #endif


 /*безопасное ожидание сигнала SIGINT, устанавливающего
   флаг flag   */
 /*установка обpаботчика, связанного с SIGINT */
        signal(SIGINT, tint);

 #ifdef BSD
 /*обpаботчик BSD       */
        sigblock(sigmask(SIGINT));
        while (flag == 0)
           sigpause(0);
 /*обpаботка сигнала    */
        ..............
 #endif

 #ifdef SYS5
 /*обpаботчик SYSTEM V  */
        sighold(SIGINT));
        while (flag == 0)
            sigpause(SIGINT);
 /*обpаботка сигнала    */
        ..............
 #endif

        exit(0);
 }

Сигналы: особые случаи

- Сигнал SIGCLD (или SIGCHLD)

Этот сигнал непосредственно связан с понятием процесса "зомби". Во избежание перехода процесса в это состояние, необходимо правильно управлять сигналом SIGCHLD в порождающем процессе. Пример эскизной схемы обработки:

ПРОГРАММА 6  /*обpаботка сигнала SIGHLD*/
 #include <signal.h>


 #ifdef BSD
 #include 
 /*обpаботка BSD, связанного с сигналом SIFHLD */
 tsigchld()
 {
    union wait status;
    while (wait3(&status, WNOHANG, 0) > 0) continue;    }
 #endif

 main()
 {
 #ifdef BSD
 /*BSD : установка хэндлеpа, связанного с SIGHLD */        signal(SIGCHLD, tsigchld);
 /*ожидание : если сигнал SIGHLD появится во вpемя
   запpоса на ожидание (read,accept,write ...),
   следует игноpиpовать ошибку, если errno=EINTR и
   веpнуться к пpеpванному обpащению к системе */
 #endif

 #ifdef SYS5
 /*System V : сигнал SIGHLD игноpиpуется */
    signal(SIGCLD,  SIG_IGN);
 #endif
 }

- Сигнал SIGALARM

Этот сигнал служит для реализации временных задержек (timeouts). Продолжительность задержки задается примитивом alarm () или setitimer (). По истечении заданного времени процессу посылается сигнал SIGALARM.

ПРОГРАММА 7
/*Использование сигнала SIGALRM для упpавления
   задеpжкой */
 #include <signal.h>


 /*обpаботчик сигнала SIGALRM       */
 tsigalrm()
 {
    printf("timeout lecture\n");
    exit(1);
 }

 main()
 {
    char buf[80];   /*буфеp   */
    int nboct;
 /*установка хэндлеpа, связанного с SIGALRM */       signal(SIGALRM, tsigalrm);
 /*цикл чтения символов или выход по тайм-аут*/      for  (;;)   {
 /*установка вpеменной задеpжки в 10 секунд */       alarm(10);
    nboct = read(1, buf, sizeof(buf));
    buf[nboct] = '\0';
    printf("buffer recu %s \n", buf);
  }
 }

- Сигнал SIGIO (или SIGPOLL)

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

Не-блокирующие операции

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

ПРОГРАММА 8
 /*Блокиpующий ввод-вывод */

 #include <fcntl.h>
 #include <stdio.h>
 #include <sys/ioctl.h>

 main()
 {
   int fd;                     /*дескpиптоp файла */
   int on = 1, off = 0;        /*пеpеменная для ioct1()*/
   char buf[80];               /*буфеp      */

 /*используется stdin          */
                        fd = 0;

 /*сначала оpганизуется неблокиpующий ввод-вывод, а затем
   блокиpующий */

 #ifdef BSD
 /*обpаботчик BSD         */
 /*неблокиpующий ввод-вывод */
                        fcntl(fd, F_SETFL, FNDELAY|fcntl(fd, F_GETFL, 0));
                        if (read(fd, buf, sizeof(buf)) < 0) perror("rien a lire");
 /*блокиpующий ввод-вывод */
                        fcntl(fd, F_SETFL,~FNDELAY&fcntl(fd, F_GETFL, 0));

                                if (read(fd, buf, sizeof(buf)) < 0) perror("erreur");
 /*можно также использовать ioct1 */
 /*неблокиpующий ввод-вывод */
                                ioctl(fd, FIONBIO, &on);
 /*блокиpующий ввод-вывод */
                                ioctl(fd, FIONBIO, &off);
 #endif

 #ifdef SYS5
 /*обpаботчик System V    */
 /*неблокиpующий ввод-вывод */
                                fcntl(fd, F_SETFL, O_NDELAY|fcntl(fd, F_GETFL, 0));
                                if (read(fd, buf, sizeof(buf)) == 0) perror("rien a lire");
 /*блокиpующий ввод-вывод */
                                fcntl(fd, F_SETFL,~O_NDELAY&fcntl(fd, F_GETFL, 0));
                                if (read(fd, buf, sizeof(buf)) < 0) perror("erreur");
 #endif

                                exit(0);
 }

Асинхронный ввод-вывод

Процессы могут запросить у ядра предупреждений о возможности считывания или записи при работе с файлом. В этом случае они получают сигнал SIGIO. Для этого необходимо выполнить следующие операции : 1) установить обработчик (handler) для сигнала SIGIO; 2) обеспечить прием сигнала для Process ID или Process Group ID процесса; это осуществляется посредством примитива fcntl () или ioctl (); 3) установить для процесса опцию асинхронности, используя функ- цию fcntl ().

ПРОГРАММА 9
 /*Пpимеp асинхpонного чтения из файла stdin          */    
 #include <fcntl.h>
 #include <signal.h>


 /*обpаботчик SIGIO                */
 tsigio()
 {
                        char buf[80];             /*буфеp     */
                        int nboct;                /*число байт        */
 /*чтение из стандаpтного ввода      */
                        nboct = read(1, buf, sizeof(buf));
                        buf[nboct] = '\0';
                        printf("buffer recu %s \n", buf);
 }

 main()
 {
 /*установка хэндлеpа, связанного с SIGIO */
                        signal(SIGIO, tsigio);
 /*установка pежима пpинятия сигнала SIGIO пpоцессом  */   fcntl(0, F_SETOWN, getpid());

 /*установка pежима асихнpонного ввода-вывода для пpоцесса */  fcntl(0, F_SETFL, FASYNC);
 /*цикл, котоpый может быть пpеpван сигналом SIGIO
   ввода-вывода    */
                        for (;;)   {
 /*симуляция активности */
                        ...............

     }
 }

Мультиплексирование ввода-вывода

Используется примитив select () /BSD/ или примитив poll () / System V/. select() предполагает три вида событий: - возможность считывания для данного дескриптора;
- возможность записи для данного дескриптора;
- наступление событий для данного дескриптора, как в случае экспресс-данных для сокета (socket) (cм. главу 4 "Сокеты").
Можно задать значение временной задержки (timeout), по истечении которой ожидание прекращается. Необходимо указать дескрипторы, подлежащие проверке, установив биты в какой-либо переменной. Макросы, позволяющие управлять этими битами через переменную типа fd_set (тип определенный в файле <sys/types.h>: FD_ZERO, FD_SET, FD_CLR, FD_ISSET.

ПРОГРАММА 10
/*Мультиплексиpование с помощью функции select(),
  обеспечивающее одновpеменное ожидание чтения из
   стандаpтного ввода и из файла, связанного с дескpиптоpом 4 */
 #include <errno.h>l.h>
 #include <stdio.h>
 #include <sys/types.h>
 #include <sys/time.h>

 main()
 {
   struct timeval timeout = (60, 0);  /*тайм-аут-60
                          секунд */
   fd_set readfs;       /*пеpеменная для
                          select */
   char buf[80];        /*буфеp    */
   int i;       /*счетчик циклов*/
 #define fdio 0 /*stdin    */
 #define fdip 4 /*втоpой дескpиптоp */
 /*инициализация readfs    */
   FD_ZERO(&readfs);
 /*ожидание чтения из дескpиптоpов      */
   for (;;)  {
      FD_SET(fdio, &readfs);
      FD_SET(fdip, &readfs);
 /*select() для stdin.fdip и тайм-аута */
 /*пеpвый паpаметp указывает на то, что пpосматpиваются      дескpиптоpы с номеpами от 0 до fdip */
      switch (select(fdip+1, &readfs, 0, 0,
                    (struct timeval*) &timeout)){
      case 0;
 /*тайм-аут*/
        fprintf(stdout, " timeout \n");
        exit(1);
      default:
 /*поиск соответствующего дескpиптоpа */
        if (FD_ISSET(fdio, &readfs)) {
 /*ввод с теpминала   */
          for (i = 0; i
        read(fdip, &buf[i], 1);
                                       if (buf[i] == '\n') break;
                                    }
                                  }
                               }
                       }
 }
}


Примитив poll () осуществляет операцию того же типа. Дескрипторы и тестируемые события показаны в таблице структур pollfd:

        struct pollfd {
                        int fd;    /*тестируемый дескриптор*/

short events; /*тестируемые события на fd*/
        short revents; /*происшедшее событие на fd*/ };

Кроме того, можно задать значение временной задержки, соот- ветствующее наибольшему времени задержки.

ПРОГРАММА 11 /*Мультиплексиpование с помощью функции poll(),
   обеспечивающее одновpеменное ожидание чтения из

   стандаpтного ввода и из файла, связанного с дескpип-      тоpом 4 */
 #include 
 #include 
 #include 
 #include 

 main()
 {
   struct pollfd fds [2];             /*массив pollfd*/      char buf[80];                      /*буфеp    */
   int i;                             /*счетчик циклов*/
 #define fdio 0                       /*стандаpтный ввод */  #define fdio 4                       /*дескpиптоp 4*/     /*ожидание чтения из дескpиптоpов (POLLIN) */
                        for (;;) {

                                fds[0].fd = fdio;
                                fds[1].fd = fdip;
                                fds[0].events = pollin;
                                fds[1].events = pollin;
                                fds[0].revents = 0;
                                fds[1].revents = 0;
 /*poll() для stdin,fdip и тайм-аута    */
 /*втоpой паpаметp указывает на число пpосматpиваемых
   дескpиптоpов - вpемя тайм-аута выpажается в милли-
   секундах */
                                switch (poll(fds, 2, 60000))   {
                                case 0:
 /*тайм-аут */
                                 fprintf(stdout, " timeout \n");
                                 exit(1);
                          default:
 /*ввод с теpминала                   */
        if (fds[0].revents == pollin) {
                        for (i = 0; i
                             read(fdip, &buf[i], 1);
                            if (buf[i]  == '\n') break;
          }
        }
      }
    }
  }
}

Процесс "демон"

Демон (следящая программа) UNIX представляет собой процесс, работающий в фоновом режиме, он функционирует постоянно и не связан с каким-либо терминалом. Существует несколько способов создать демон :
- запустить его при запуске системы, включив его в файл /etc /rc. В этом случае, в программе необходимо создать процесс, окончание которого на ожидается, для того, чтобы не блокировать командный файл запуска;
- выполнить его с помощью файла crontab, что позволяет периодически контролировать его демоном cron;
- выполнить его в качестве фоновой задачи из shell, программно создав процесс, окончания которого не ожидается.
Для правильного кодирования демона необходимо соблюдать не- которые правила (см. пример в главе 4 "Сокеты"):
- закрыть все дескрипторы открытых файлов;
- выйти в корневой каталог дерева файловой системы;
- установить маску создания файлов; - отсоединиться от контрольного терминала, создав собственную группу процессов;
- игнорировать сигналы ввода-вывода;
- правильно управлять сигналом SIGCLD, для того, чтобы возможные порожденные процессы не оставались в состоянии "зомби".

1.4. БАЗОВЫЕ ПОНЯТИЯ TCP/IP

Семиуровневая модель OSI (Open System Interconnection) в настоящее время хорошо известна. Можно установить соответствие между протоколами TCP/IP и этой моделью (рис. 1.1.). Протокол TCP/IP распространены чрезвычайно широко, т.к. с его помощью в мире связано более 150000 ЭВМ, в рамках сети Internet. Формальные характеристики протоколов Internet опре- делены в RFC (Request For Comment). Эти RFC можно заказать в NIC (Network Information Center), через электронную почту (infoserver@sh.cs.net) или посредством ftp anonyme у различных каналов обслуживания: SRI-NIC.ARPA, nuri.inria.fr

Рис. 1.1. Протоколы и сервис TCP/IP

Адрес Ethernet, адрес Internet, имя компьютера

Всякая плата Ethernet снабжена адресом Ethernet - уникальным адресом, определяемым предприятием-изготовителем. Машина может быть оснащена несколькими платами и, следовательно, иметь несколько адресов Ethernet. В сети IP машина обозначена адресом Internet, уникальным для данной сети. Этот адрес имеет 32-битовое кодирование и содержит два поля, определяющие идентификатор сети и идентификатор машины. В NIC (Network Information Center) можно получить "официальный" адрес. Этот адрес Internet связан с именем (строкой ASCII-символов) в файле /etc/host. Имя должно быть уникальным для данной сети.

Широковещательный адрес

Сеть Ethernet обладает широковещательным адресом: кадр Ethernet, т.о. могут получить все машины данной сети. Широковещательный адрес Internet позволяет использовать эту возможность: его обычно получают, устанавливая в 1 все биты соответствующей части сетевого адреса. Широковещательные кадры не пересекают маршрутизаторов (см. ниже)

Маршрутизация

Две машины ведут диалог непосредственно только в случае, если поля "сетевого адреса" их адресов Internet одинаковы (та же физическая сеть). В противном случае, необходимо ввести между ними маршрутизатор. Маршрутизатором называется устройство, подсоединенное по крайней мере к двум сетям, адреса Internet которых различны. Машинную часть адреса Internet можно разделить на адрес базовой сети и машинный адрес в этой базовой сети (subnet). Меж- ду двумя машинами с различными адресами базовых сетей необходимо установить маршрутизатор.

Сетевой уровень IP (Internet Protocol)

Уровень IP (Internet Protocol) обеспечивает рассылку дейтаграмм. Контроль потока весьма ограничен: посылка сообщения ICMP (Internet Control Message Protocol) на передающее устройство при насыщении машины. Согласование между адресом Internet и адресом Ethernet машины осуществляется посредством протокола ARP (Address Resolution Protocol). Используется передача Ethernet: машина, распознающая свой адрес Internet, отвечает, выдавая соответствующий адрес Ethernet.

Транспортный уровень TCP (Transmission Control Protocol)

TCP (Transmission Control Protocol) представляет собой на- дежное средство передачи данных, с установлением виртуального соединения, позволяющее вести обмен потоками байтов. Он устанавливает и поддерживает связь между двумя системами, проверяет заданную последовательность принимаемых и передаваемых бло- ков, обеспечивает контроль потока. Предусмотрено применение TCP в случае фатальной ошибки при передаче данных (см. главу 4 "Сокеты").

Транспортный уровень UDP (User Datagram Protocol)

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

Службы и номера портов

Выше транспортного уровня (TCP или UDP) находится уровень обслуживания. Каждая служба идентифицируется номером, называе- мом номером порта. Этот номер кодируется целым числом. Существуют виды обслуживания, использующие известные порты, одинаковые на всех машинах и определяемые в файле /etc/services. Службе может присваиваться номер порта, при условии, что номер не находится в пределах от 1 до 1023 (диапазон, закреплен- ный за системным сервисом) и не является уже присвоенным другой службе. Служба также может попросить систему присвоить ей номер порта; речь идет о временном номере, который будет использоваться только в течении срока жизни службы.

Связывание

Связь между двумя службами TCP/IP, размещенными на двух машинах (machi 1 и machi 2), производится по пятиэлементной схеме.

где:
- трансп.протокол: TCP или UDP - адрес 1 : адрес Internet machi 1 - ном.порта 1 : номер сервисного порта на machi 1 - адрес 2 : адрес Internet machi 2 - ном.порта 2 : номер сервисного порта на machi 2
Такая пятиэлементная схема называется связыванием. Следовательно, для установления связи с удаленной машиной необходимо:
- присвоить номер порта (или предоставить это сделать системе);
- определить адрес Internet машины, с которой должен осуществляться обмен данными;
- знать или определить сервисный порт службы. В следующих главах мы увидим, какими средствами выполняются эти операции.

Буферы TCP

TCP обеспечивает побайтовую передачу данных. Понятия сообще- ний не существует. Эти данные упорядочиваются в буферных ЗУ (buffers) и затем передаются на уровень IP (рис.1.2.). На каждый случай связи имеется одно буферное ЗУ передачи и одно - приема. Механизм упорядочивания в буферных ЗУ достаточно сложен и зависит, в частности, от управления потоком. Получаемые данные принимаются в упорядоченном состоянии, но в каком виде они бы- ли переданы в сеть - неизвестно. Передающее устройство может, таким образом, послать n - байтов, а получатель получит только x - байтов (x < n). Потребуется возобновление операции считывания до получения n - байтов. Мы вернемся к этому механизму в главе 4 "Сокеты". Этим механизмом можно управлять с помощью TCP-опции (push). Можно также послать экспресс-данные в обход данного механизма (out-of-band data, рис.1.3.). Это может быть использовано для посылки, например, прерывания.


Рис. 1.2.Буферные ЗУ TCP

Рис.1.3. Экспресс-данные TCP

Суперсервер inetd

Речь идет о демоне (следящей программе), активизирующем нуж- ную сервисную программу; он управляется файлом конфигурации /etc/inetd.conf. Этот файл содержит, в частности:
- название службы;
- используемый транспортный протокол;
- имя исполняемого файла.
В главе 4 "Сокеты" мы увидим, как работает этот демон.

1.5. ИТОГИ

UNIX и TCP/IP представляют собой два фактических стандарта.

Основной активной единицей системы UNIX является процесс. Процессы создаются системным вызовом fork (), идентифициру- ются номером PID и принадлежат группе процессов. Процессу или группе процессов можно послать сигнал, являю- щийся программным прерыванием. Необходимо отличать вводы-выводы в режиме буферизации (ис- пользование стандартной библиотеки Си) и вводы выводы без буферизации (использование библиотеки UNIX). Протоколы TCP/IP предлагают два вида транспортных услуг:
- TCP/IP: с установлением логического соединения, позволяющий вести надежный обмен байтовыми потоками;
- UDP: без установления логического соединения, позволяющий вести ненадежный обмен сообщениями. Служба определяется номером порта (идентификатор с целым значением).
Для установления связи с удаленной службой необходимо определить или знать адрес Internet удаленной машины и номер порта службы. TCP включает в себя буферизации.БИБЛИОГРАФИЯ

Источником вдохновения при написании данной главы служила [STEVENS 90].

Для тех, кто хотел бы узнать побольше о концепциях и реали- зации UNIX в качестве справочных руководств рекомендуется [BACH 89] и [LEFFLER 89].

Описанию основных понятий UNIX посвящено довольно много руководств :
[KERNIGHAN 86],[RIFFLET 89],[ROCKIND 88],[RIFFLET 90],[CURRY 90] ...
Что касается TCP/IP - то, в качестве введения можно использовать RFC