Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

ОСиС_2008

.pdf
Скачиваний:
96
Добавлен:
29.05.2015
Размер:
2.65 Mб
Скачать

9. Лабораторный курс

 

 

345

sigdelset (&set1, SIGINT);

/* Удалить из набора SIGINT */

sigdelset (&set1, SIGQUIT);

/*

--//--

SIGQUIT */

sigprocmask (SIG_SETMASK, &set1, NULL);

 

. . . . . . . . . . . .

/* Критический участок кода */

sigprocmask (SIG_UNBLOCK, &set1, NULL);

 

}

 

 

 

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

9.7.4. Сеансы и группы процессов

Рассмотрим системные вызовы, позволяющие процессу определять идентификаторы своих сеанса и группы, а также создавать новые их экземпляры. Идентификатор сеанса можно узнать с помощью функции getsid:

#include <sys/types.h> #include <unistd.h> pid_t getsid(pid_t pid);

Единственный параметр функции (pid) представляет собой номер того процесса, для которого требуется определить номер сеанса. Искомый процесс может не совпадать с процессом, содержащим функцию getsid. Единственное требование: оба процесса должны принадлежать к одному сеансу.

Создание нового сеанса выполняет функция setsid:

#include <sys/types.h> #include <unistd.h> pid_t setsid(void);

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

Для определения идентификатора своей группы процесс должен выполнить системный вызов getpgrp:

#include <sys/types.h> #include <unistd.h> pid_t getpgrp(void);

Системный вызов setpgid позволяет процессу стать членом существующей группы или создать новую группу:

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

346

Одиноков В.В., Коцубинский В.П.

int setpgid(pid_t pid, pid_t pgid);

Данная функция может быть применена процессом по отношению к самому себе или к своему потомку при условии, что потомок не выполнил вызов exec, запускающий на выполнение другую программу. В результате выполнения функции setpgid процесс pid принадлежит к группе с идентификатором pgid.

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

О п р е д е л и т е идентификаторы сеансов и групп для своих ранее созданных процессов. Запустите процесс, создающий новый сеанс и новую группу.

9.7.5. Посылка сигналов другим процессам

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

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

int kill(pid_t pid, int sig);

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

1) если pid положителен, то сигнал посылается процессу

сидентификатором pid;

2)если pid равен нулю, то сигнал посылается всем процессам, принадлежащим к той же группе, что и процесс-отправитель (включая и его самого);

3)если pid равен (–1), то сигнал посылается всем процессам, принадлежащим тому же пользователю, что и процесс-отправитель (включая и его самого);

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

Так как при применении kill обычно требуется знание pid конкретного процесса, то передача сигналов обычно производится

9. Лабораторный курс

347

между близкими родственниками, например между процессомотцом и дочерним процессом. Если процесс пытается послать сигнал процессу, принадлежащему другому пользователю, то вызов kill возвратит значение (–1). Несмотря на то что данный вызов позволяет передавать любые сигналы, на практике обычно передаются лишь сигналы SIGUSR1, SIGUSR2, так как передача с помощью kill других сигналов может внести путаницу в обработку таких же сигналов, поступающих от их настоящих источников.

Пример

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

Программа:

#include <unistd.h> #include <signal.h> int ntimes = 0;

/* Функция-обработчик сигнала SIGUSR1 в процессе-отце */ void p_action (int sig)

{

printf(“Процесс-отец получил сигнал #%d\n”, ++ntimes);

}

/* Функция-обработчик сигнала SIGUSR1 в дочернем процессе */

void c_action (int sig)

{

printf(“Дочерний процесс получил сигнал #%d\n”, ++ ntimes);

}

main()

{

pid_t pid, ppid;

static struct sigaction pact, cact;

/* Изменение диспозиции сигнала SIGUSR1 в процессеотце */

pact.sa_handler = p_action ; /* Запись адреса обработчика */ sigfillset(&(pact.sa_mask)); /* Создать полный набор

сигналов */

348

Одиноков В.В., Коцубинский В.П.

sigaction(SIGUSR1, &pact, NULL);

switch (pid = fork()) {

 

case

-1:

 

perror(“Ошибка”);

 

exit(1);

 

case

0:

/* Дочерний процесс */

/* Изменение диспозиции сигнала SIGUSR1 в дочернем

процессе */

 

cact.sa_handler = c_action ;

/* Запись адреса

 

 

обработчика */

sigfillset(&(cact.sa_mask));

/* Создать полный набор

 

 

сигналов */

sigaction(SIGUSR1, &сact, NULL);

/* Получение идентификатора процесса-отца */ ppid = getppid();

/* Бесконечный цикл */ for ( ; ; )

{

sleep(1);

kill(ppid, SIGUSR1); pause ();

}

default: /* Процесс-отец */ /* Бесконечный цикл */

for ( ; ; )

{

pause (); sleep(1);

kill(pid, SIGUSR1);

}

}

}

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

П р о в е р ь т е правильность выполнения приведенной программы.

9.7.6. Сигнал таймера

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

9. Лабораторный курс

349

невелика. Намного полезнее для внутрипроцессного использования системный вызов alarm, который устанавливает для процесса таймер интервала. Вызов alarm выполняет запись в эту переменную первоначального значения, определяющего требуемый интервал времени. По истечении этого времени обработчик прерывания аппаратного таймера (не следует путать с таймером интервала) выдает в процесс сигнал SIGALRM. Определение вызова alarm:

#include <unistd.h>

unsigned int alarm(unsigned int sec);

Параметр sec задает время в секундах, на которое устанавливается таймер. В отличие от вызова sleep, вызов alarm не приостанавливает выполнение процесса. До тех пор пока не придет сигнал SIGALRM, никакого воздействия на это выполнение не будет. Выключить таймер можно при помощи вызова alarm с нулевым параметром.

Особенно полезно применение вызова alarm для ограничения времени выполнения какой-то операции. При этом, сделав вызов alarm, процесс начинает выполнение операции. Если она завершается вовремя, то таймер сбрасывается. Иначе — с помощью сигнала SIGALRM процесс прерывается и выполняет корректирующее действие.

9.7.7. Задание

Разработать одну из следующих программ (по указанию преподавателя).

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

2.Процесс-отец порождает четыре дочерних процесса, каждый из которых выполняет бесконечный цикл. При этом каждый из дочерних процессов особым образом реагирует на сигнал SIGINT:

процесс 1 при получении сигнала SIGINT выводит сообщение на экран и продолжается;

350

Одиноков В.В., Коцубинский В.П.

процесс 2 обрабатывает сигнал SIGINT в бесконечном цикле, выдавая свое сообщение на экран. Данный цикл защищен от воздействия сигнала SIGQUIT;

вся программа процесса 3 защищена от воздействия сигнала

SIGINT;

перед входом процесса 4 в бесконечный цикл для него меняется идентификатор сеанса.

Сразу после порождения дочерних процессов процесс-отец завершается.

Далее следует проверить реакцию оставшихся процессов на сигналы SIGINT и SIGQUIT.

3. Процесс-отец открывает существующий текстовый файл, а затем порождает два дочерних процесса, которые по очереди выводят содержимое этого файла фиксированными порциями по 10 символов, предваряя каждый вывод номером своего процесса. Вывод на экран заканчивается или при достижении конца файла, или по истечении интервала времени в 10 секунд.

Примечание. Для получения номера своего процесса можно использовать функцию getpid:

#include <sys/types.h> pid_t getpid(void);

9.8. Лабораторная работа № 8. Управление терминалом

Целью выполнения настоящей лабораторной работы является получение навыков по программному управлению терминалом.

9.8.1. Функции терминальной линии

Напомним, что терминал — устройство, выполняющее ввод и вывод символьной информации. Чаще всего это совокупность клавиатуры (устройства ввода) и экрана (устройства вывода), но могут использоваться и другие символьные устройства. Подобно другим периферийным устройствам, терминал представлен в файловой структуре системы специальным файлом в каталоге /dev.

Примеры имен таких файлов: /dev/console, /dev/tty03, /dev/tty.

Последнее имя является «собирательным», позволяя в любой программе обращаться к своему управляющему терминалу стандартным образом.

9. Лабораторный курс

351

Если пользователь использует не терминал системы UNIX, а работает на терминале другой ЭВМ, соединенной с UNIX-системой каналами связи, то UNIX предоставляет в распоряжение такого пользователя псевдотерминал, который с точки зрения пользователя и его программных процессов в UNIX-системе выглядит точно так же, как и реальный терминал. Поэтому сказанное далее о терминалах справедливо также и для псевдотерминалов.

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

1)вывод на экран терминала «эха» символов, вводимых с клавиатуры. «Эхо» — изображение на экране той клавиши, которая была только что нажата. Наличие данной функции обусловлено тем, что устройство ввода (клавиатура) и устройство вывода (экран) никак не связаны между собой на аппаратном уровне;

2)предоставление возможности пользователю выполнять простейшее редактирование информации, вводимой с клавиатуры. Примером является удаление символов, расположенных левее или правее позиции, отмеченной курсором;

3)выдача сигналов (см. лабораторную работу № 7) при нажатии пользователем определенных клавиш или их комбинаций.

Для выполнения перечисленных функций между процессом

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

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

352

Одиноков В.В., Коцубинский В.П.

Процесс

Канонический

Дисциплина

линии

буфер

termios

 

Буфер ввода

Буфер вывода

 

Ввод

Вывод

Драйвер терминала

Клавиатура

Экран

Рис. 68. Информационные потоки в терминальной линии

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

Дисциплина линии (и вся терминальная линия) может работать

вдвух режимах: каноническом и неканоническом. В каноническом режиме, во-первых, дисциплина линии помещает каждый вводимый с клавиатуры (с помощью драйвера) символ одновременно

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

9. Лабораторный курс

353

На любом экране всегда присутствует изображение курсора. Курсор — светящийся прямоугольник, предназначенный для того чтобы указывать на ту позицию экрана, в которой будет показан следующий символ, выбранный драйвером из очереди вывода. Курсор генерируется аппаратурой экрана, а его координаты задаются драйвером. Пользователь перемещает курсор в пределах экранной строки с помощью клавиш <Å> и <Æ>. Чем больше раз пользователь нажмет соответствующую клавишу, тем на большее число позиций переместится курсор. Это происходит благодаря тому, что при каждом нажатии клавиши драйвер передает ее код ASCII в дисциплину линии, которая, во-первых, помещает код символа в буфер вывода, а во-вторых, корректирует свой внутренний указатель на элемент (символ) буфера ввода.

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

Результатом работы дисциплины линии в каноническом режиме при вводе являются отредактированные строки символов, каждая из которых всегда заканчивается символом nl. (Здесь и далее без угловых скобок будем записывать обозначения символов, принятые в UNIX или в СИ.)

Что касается вывода в каноническом режиме, то дисциплина линии ограничивается лишь добавлением к каждому символу nl символа carriage-return (возврат каретки). В результате каждая выводимая строка будет начинаться с левого края экрана.

Канонический режим терминальной линии используют, например, интерпретатор команд shell, а также строковые текстовые редакторы ed, sed, vi и т.д.

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

354

Одиноков В.В., Коцубинский В.П.

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

9.8.2. Специальные символы редактирования и выдачи сигналов

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

erase — символ, приводящий к стиранию предыдущего символа в строке. В качестве данного символа пользователь может задать любой код ASCII. Чаще всего для этого используется код клавиши <Backspace>. Например, если в командной строке UNIX набрать

$ whp<Backspace>o<Enter>

то интерпретатору команд будет передана строка who;

kill — стирание всех символов до начала строки. По умолчанию для получения символа kill одновременно нажимаются две клавиши <Ctrl>&<?>. Другие используемые комбинации:

<Ctrl>&<X> и <Ctrl>&<U>;

nl обычный разделитель строк. Он всегда имеет значение символа line-feed (перевод строки). Этот же код ASCII имеет символ СИ newline (новая строка);

stop — используется для временной приостановки вывода на терминал. Это позволяет приостановить вывод прежде, чем выводимый текст исчезнет за границей экрана. Обычно используется комбинация клавиш <Ctrl>&<S>. Лишь в некоторых системах может быть изменен пользователем;

start — используется для продолжения вывода, приостановленного символом stop. Если stop не был введен, то start игнорируется. Для получения символа start обычно используется <Ctrl>&<Q>. Лишь в некоторых системах может быть изменен пользователем;

eof — окончание входного потока с терминала (этот символ должен быть единственным символом в начале новой строки). Стандартным значением является символ ASCII eot, получаемый нажатием <Ctrl>&<D>;

intr — символ прерывания. Ввод данного символа пользователем приводит к посылке всем процессам оперативной группы сеанса, управляемого данным терминалом, сигнала SIGINT. Стандартная реакция процесса на такой сигнал — завершение. Обыч-