скачать рефераты
  RSS    

Меню

Быстрый поиск

скачать рефераты

скачать рефератыРеферат: Администрирование локальных сетей

Схема с синхронными событиями очень распространена. Кассир сидит у кассы и ожидает, пока к нему в окошечко не заглянет клиент. Поезд периодически проезжает мимо светофора и останавливается, если горит красный. Функция Си пассивно "спит" до тех пор, пока ее не вызовут; однако она всегда готова выполнить свою работу (обслужить клиента). Такое ожидающее заказа (события) действующее лицо называется сервер. После выполнения заказа сервер вновь переходит в состояние ожидания вызова. Итак, если событие ожидается в специальном месте и в определенные моменты времени (издается некий вызов для ОПРОСА) - это синхронные события. Канонический пример - функция gets, которая задержит выполнение программы, пока с клавиатуры не будет введена строка. Большинство ожиданий внутри системных вызовов - синхронны. Ядро ОС выступает для программ пользователей в роли сервера, выполняющего сисвызовы (хотя и не только в этой роли - ядро иногда предпринимает и активные действия: передача процессора другому процессу через определенное время (режим разделения времени), убивание процесса при ошибке, и.т.п.).

Сигналы - это асинхронные события. Они приходят неожиданно, в любой момент времени - вроде телефонного звонка. Кроме того, их не требуется заказывать - сигнал процессу может поступить совсем без повода. Аналогия из жизни такова: человек сидит и пишет письмо. Вдруг его окликают посреди фразы - он отвлекается, отвечает на вопрос, и вновь продолжает прерванное занятие. Человек не ожидал этого оклика (быть может, он готов к нему, но он не озирался по сторонам специально). Кроме того, сигнал мог поступить когда он писал 5-ое предложение, а мог - когда 34-ое. Момент времени, в который произойдет прерывание, не фиксирован.

Сигналы имеют номера, причем их количество ограничено - есть определенный список допустимых сигналов. Номера и мнемонические имена сигналов перечислены в includeфайле <signal.h> и имеют вид SIGнечто. Допустимы сигналы с номерами 1..NSIG-1, где NSIG определено в этом файле. При получении сигнала мы узнаем его номер, но не узнаем никакой иной информации: ни от кого поступил сигнал, ни что от нас хотят. Просто "звонит телефон". Чтобы получить дополнительную информацию, наш процесс должен взять ее из другого известного места; например - прочесть заказ из некоторого файла, об имени которого все наши программы заранее "договорились". Сигналы процессу могут поступать тремя путями:

  • От другого процесса, который явно посылает его нам вызовом

         kill(pid, sig);

где pid - идентификатор (номер) процесса-получателя, а sig - номер сигнала. Послать сигнал можно только родственному процессу - запущенному тем же пользователем.

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

Процесс-получатель должен как-то отреагировать на сигнал. Программа может:

  • проигнорировать сигнал (не ответить на звонок);
  • перехватить сигнал (снять трубку), выполнить какие-то действия, затем продолжить прерванное занятие;
  • быть убитой сигналом (звонок был подкреплен броском гранаты в окно);

В большинстве случаев сигнал по умолчанию убивает процесс-получатель. Однако процесс может изменить это умолчание и задать свою реакцию явно. Это делается вызовом signal:

    #include <signal.h>

    void (*signal(int sig, void (*react)() )) ();

Параметр react может иметь значение:

SIG_IGN

сигнал sig будет отныне игнорироваться. Некоторые сигналы (например SIGKILL) невозможно перехватить или проигнорировать.

SIG_DFL

восстановить реакцию по умолчанию (обычно - смерть получателя). имя_функции Например

          void fr(gotsig){ ..... }  /* обработчик */

          ... signal (sig, fr); ... /* задание реакции */

Тогда при получении сигнала sig будет вызвана функция fr, в которую в качестве аргумента системой будет передан номер сигнала, действительно вызвавшего ее gotsig==sig. Это полезно, т.к. можно задать одну и ту же функцию в качестве реакции для нескольких сигналов:

          ... signal (sig1, fr); signal(sig2, fr); ...

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

Приведем список некоторых сигналов; полное описание посмотрите в документации. Колонки таблицы: G - может быть перехвачен; D - по умолчанию убивает процесс (k), игнорируется (i); C - образуется дамп памяти процесса: файл core, который затем может быть исследован отладчиком adb; F - реакция на сигнал сбрасывается; S - посылается обычно системой, а не явно.

    сигнал         G   D   C   F   S  смысл

    SIGTERM        +   k   -   +   -  завершить процесс

    SIGKILL        -   k   -   +   -  убить процесс

    SIGINT         +   k   -   +   -  прерывание с клавиш

    SIGQUIT        +   k   +   +   -  прерывание с клавиш

    SIGALRM        +   k   -   +   +  будильник

    SIGILL         +   k   +   -   +  запрещенная команда

    SIGBUS         +   k   +   +   +  обращение по неверному

    SIGSEGV        +   k   +   +   +     адресу

    SIGUSR1, USR2  +   i   -   +   -  пользовательские

    SIGCLD         +   i   -   +   +  смерть потомка

  • Сигнал SIGILL используется иногда для эмуляции команд с плавающей точкой, что происходит примерно так: при обнаружении "запрещенной" команды для отсутствующего процессора "плавающей" арифметики аппаратура дает прерывание и система посылает процессу сигнал SIGILL. По сигналу вызывается функция-эмулятор плавающей арифметики (подключаемая к выполняемому файлу автоматически), которая и обрабатывает требуемую команду. Это может происходить много раз, именно поэтому реакция на этот сигнал не сбрасывается.
  • SIGALRM посылается в результате его заказа вызовом alarm() (см. ниже).
  • Сигнал SIGCLD посылается процессу-родителю при выполнении процессом-потомком сисвызова exit (или при смерти вследствие получения сигнала). Обычно процессродитель при получении такого сигнала (если он его заказывал) реагирует, выполняя в обработчике сигнала вызов wait (см. ниже). По-умолчанию этот сигнал игнорируется.
  • Реакция SIG_IGN не сбрасывается в SIG_DFL при приходе сигнала, т.е. сигнал игнорируется постоянно.
  • Вызов signal возвращает старое значение реакции, которое может быть запомнено в переменную вида void (*f)(); а потом восстановлено.
  • Синхронное ожидание (сисвызов) может иногда быть прервано асинхронным событием (сигналом), но об этом ниже.

Деления просесса

Системный вызов fork() (вилка) создает новый процесс: копию процесса, издавшего вызов. Отличие этих процессов состоит только в возвращаемом fork-ом значении:

    0                   - в новом процессе.

    pid нового процесса - в исходном.

Вызов fork может завершиться неудачей если таблица процессов переполнена. Простейший способ сделать это:

    main(){

          while(1)

            if( ! fork()) pause();

    }

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

Пайпы и FIFO-файлы.

Процессы могут обмениваться между собой информацией через файлы. Существуют файлы с необычным поведением - так называемые FIFO-файлы (first in, first out), ведущие себя подобно очереди. У них указатели чтения и записи разделены. Работа с таким файлом напоминает проталкивание шаров через трубу - с одного конца мы вталкиваем данные, с другого конца - вынимаем их. Операция чтения из пустой "трубы" проиостановит вызов read (и издавший его процесс) до тех пор, пока кто-нибудь не запишет в FIFOфайл какие-нибудь данные. Операция позиционирования указателя - lseek() - неприме- нима к FIFO-файлам. FIFO-файл создается системным вызовом

    #include <sys/types.h>

    #include <sys/stat.h>

       mknod( имяФайла, S_IFIFO | 0666, 0 );

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

Разновидностью FIFO-файла является безымянный FIFO-файл, предназначенный для обмена информацией между процессом-отцом и процессом-сыном. Такой файл - канал связи как раз и называется термином "труба" или pipe. Он создается вызовом pipe:

    int conn[2];   pipe(conn);

Если бы файл-труба имел имя PIPEFILE, то вызов pipe можно было бы описать как

    mknod("PIPEFILE", S_IFIFO | 0600, 0);

    conn[0] = open("PIPEFILE", O_RDONLY);

    conn[1] = open("PIPEFILE", O_WRONLY);

    unlink("PIPEFILE");

При вызове fork каждому из двух процессов достанется в наследство пара дескрипторов:

                 pipe(conn);

                   fork();

    conn[0]----<----    ----<-----conn[1]

                    FIFO

    conn[1]---->----    ---->-----conn[0]

     процесс A                 процесс B

Пусть процесс A будет посылать информацию в процесс B. Тогда процесс A сделает:

    close(conn[0]);

    // т.к. не собирается ничего читать

    write(conn[1], ... );

а процесс B

    close(conn[1]);

    // т.к. не собирается ничего писать

    read (conn[0], ... );

Получаем в итоге:

    conn[1]---->----FIFO---->-----conn[0]

     процесс A                 процесс B

Обычно поступают еще более элегантно, перенаправляя стандартный вывод A в канал conn[1]

    dup2 (conn[1], 1); close(conn[1]);

    write(1, ... );   /* или printf */

а стандартный ввод B - из канала conn[0]

    dup2(conn[0], 0); close(conn[0]);

    read(0, ... );    /* или gets */

Это соответствует конструкции

         $   A | B

записанной на языке СиШелл.

Файл, выделяемый под pipe, имеет ограниченный размер (и поэтому обычно целиком оседает в буферах в памяти машины). Как только он заполнен целиком - процесс, пишущий в трубу вызовом write, приостанавливается до появления свободного места в трубе. Это может привести к возникновению тупиковой ситуации, если писать программу неаккуратно. Пусть процесс A является сыном процесса B, и пусть процесс B издает вызов wait, не закрыв канал conn[0]. Процесс же A очень много пишет в трубу conn[1]. Мы получаем ситуацию, когда оба процесса спят:

A потому что труба переполнена, а процесс B ничего из нее не читает, так как ждет окончания A;

B потому что процесс-сын A не окончился, а он не может окончиться пока не допишет свое сообщение.

Решением служит запрет процессу B делать вызов wait до тех пор, пока он не прочитает ВСЮ информацию из трубы (не получит EOF). Только сделав после этого close(conn[0]); процесс B имеет право сделать wait.

Если процесс B закроет свою сторону трубы close(conn[0]) прежде, чем процесс A закончит запись в нее, то при вызове write в процессе A, система пришлет процессу A сигнал SIGPIPE - "запись в канал, из которого никто не читает".

Нелокальный переход.

Теперь поговорим про нелокальный переход. Стандартная функция setjmp позволяет установить в программе "контрольную точку"*, а функция longjmp осуществляет прыжок в эту точку, выполняя за один раз выход сразу из нескольких вызванных функций (если надо)*. Эти функции не являются системными вызовами, но поскольку они реализуются машинно-зависимым образом, а используются чаще всего как реакция на некоторый сигнал, речь о них идет в этом разделе. Вот как, например, выглядит рестарт программы по прерыванию с клавиатуры:

    #include <signal.h>

    #include <setjmp.h>

    jmp_buf jmp;  /* контрольная точка */

    /* прыгнуть в контрольную точку */

    void onintr(nsig){ longjmp(jmp, nsig); }

    main(){

       int n;

       n = setjmp(jmp);  /* установить контрольную точку */

       if( n ) printf( "Рестарт после сигнала %d\n", n);

       signal (SIGINT, onintr);     /* реакция на сигнал */

       printf("Начали\n");

       ...

    }

setjmp возвращает 0 при запоминании контрольной точки. При прыжке в контрольную точку при помощи longjmp, мы оказываемся снова в функции setjmp, и эта функция возвращает нам значение второго аргумента longjmp, в этом примере - nsig.

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

Разделяемая память

shmget создает новый сегмент разделяемой памяти или находит существующий сегмент с тем же ключом shmat подключает сегмент с указанным дескриптором к виртуальной памяти обращающегося процесса shmdt отключает от виртуальной памяти ранее подключенный к ней сегмент с указанным виртуальным адресом начала shmctl служит для управления параметрами, связанными с существующим сегментом После подключения сегмента разделяемой памяти к виртуальной памяти процесса, он может обращаться к соответствующим элементам памяти с использованием обычных машинных команд чтения и записи

shmid = shmget(key, size, flag);

  • size определяет желаемый размер сегмента в байтах
  • если в таблице разделяемой памяти находится элемент, содержащий заданный ключ, и права доступа не противоречат текущим характеристикам процесса, то значением системного вызова является дескриптор существующего сегмента
  • реальный размер сегмента можно узнать с помощью системного вызова shmctl
  • иначе создается новый сегмент с размером не меньше установленного в системе минимального размера сегмента разделяемой памяти и не больше установленного максимального размера
  • создание сегмента не означает немедленного выделения под него основной памяти
  • откладывается до выполнения первого системного вызова подключения сегмента к виртуальной памяти некоторого процесса
  • при выполнении последнего системного вызова отключения сегмента от виртуальной памяти соответствующая основная память освобождается

virtaddr = shmat(id, addr, flags);

Страницы: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31


Новости

Быстрый поиск

Группа вКонтакте: новости

Пока нет

Новости в Twitter и Facebook

  скачать рефераты              скачать рефераты

Новости

скачать рефераты

© 2010.