Системное программирование в UNIX средствами Free Pascal

       

Введение


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

К счастью, ОС UNIX имеет развитые механизмы межпроцессного взаимодействия. В этой и следующей главах мы обсудим три наиболее популярных средства: сигналы (signals), программные каналы (pipes) и именованные каналы (FIFO). Данные средства, наряду с более сложными средствами, которым будут посвящены главы 8 и 10, предоставляют разработчику программного обеспечения широкий выбор средств построения многопроцессных систем.

Эта глава будет посвящена изучению первого средства – сигналам. Рассмотрим пример запуска команды UNIX, выполнение которой, вероятно, займет много времени:

$ fpc verybigprog.pas

Позже становится ясным, что программа содержит ошибку, и ее компиляция не может завершиться успехом. Тогда, чтобы сэкономить время, следует прекратить выполнение команды нажатием специальной клавиши прерывания задания (interrupt key) терминала; обычно это клавиша Del

или клавиатурная комбинация Ctrl+C.

Выполнение программы прекратится, и программист вернется к приглашению ввода команды командного интерпретатора.

В действительности при этом происходит следующая последовательность событий: часть ядра, отвечающая за ввод с клавиатуры, распознает символ прерывания задания. Затем ядро посылает сигнал SIGINT всем процессам, для которых текущий терминал является управляющим терминалом. Среди этих процессов будет и экземпляр компилятора cc. Когда процесс компилятора ее получит этот сигнал, он выполнит связанное с сигналом SIGINT действие по умолчанию – завершит работу. Интересно отметить, что сигнал SIGINT посылается и процессу оболочки, тоже связанному с терминалом. Тем не менее процесс оболочки благоразумно игнорирует этот сигнал, поскольку он должен продолжать работу для интерпретации последующих команд. Как будет рассмотрено далее, пользовательские




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

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

$ badprog



illegal instruction - core dumped

Смысл термина core dumped (сброс образа памяти) будет объяснен ниже.

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

$ fpc verybigprog.pas &

[1] 1098

а затем решает завершить ее работу. Тогда, чтобы послать процессу сигнал SIGTERM, можно использовать команду kill. Так же, как и сигнал SIGINT, сигнал SIGTERM завершит процесс, если в процессе не переопределена стандартная реакция на этот сигнал. В качестве аргумента команды kill должен быть задан идентификатор процесса:

$ kill 1098

Terminated

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

Процесс может выполнять три действия с сигналами, а именно:



изменять свою реакцию на поступление определенного сигнала (изменять обработку сигналов);

–        блокировать сигналы (то есть откладывать их обработку) на время выполнения определенных критических участков кода;

–        посылать сигналы другим процессам.

Каждое из этих действий будет рассмотрено далее в этой главе.


Содержание раздела