int mknod(char *pathname, mode_t mode, dev_t dev);
Где pathname – имя файла в файловой системе (имя FIFO), mode – флаги владения, прав доступа и т.д., dev при создании FIFO игнорируется. FIFO может быть создан и из командной строки shell:
$ mknod name p
После создания FIFO может быть открыт для записи и чтения, причём запись и чтение могут происходить в разных независимых процессах. Каналы FIFO и обычные каналы работают по следующим правилам:
1.При чтении меньшего числа байтов, чем находится в канале или FIFO, возвращается требуемое число байтов, остаток сохраняется для последующих чтений.
2.При чтении большего числа байтов, чем находится в канале или FIFO, возвращается доступное число байтов. Процесс, читающий из канала, должен соответствующим образом обработать ситуацию, когда прочитано меньше, чем заказано.
3.Если канал пуст и ни один процесс не открыл его на запись, при чтении из канала будет получено 0 байтов. Если один или более процессов открыли канал для записи, вызов read будет заблокирован до появления данных (если для канала или FIFO не установлен флаг отсутствия блокирования O_NDELAY).
4.Запись числа байтов, меньшего ёмкости канала или FIFO, гарантированно атомарно. Это означает, что в случае, когда несколько процессов одновременно записывают в канал, порции данных от этих процессов не перемешиваются.
5.При записи большего числа байтов, чем это позволяет канал или FIFO, вызов write блокируется до освобождения требуемого места. При этом атомарность операции не гарантируется. Если процесс пытается записать данные в канал, не открытый ни одним процессом на чтение, процессу генерируется сигнал SIGPIPE, а вызов write возвращает 0 с установкой ошибки (errno=EPIPE) (если процесс не установил обработку сигнала SIGPIPE, производится обработка по умолчанию, и процесс завершается).
Вкачестве примера приведём простейшую программу типа клиент-сервер, использующую FIFO для обмена данными. Следуя традиции, клиент посылает серверу сообщение «Здравствуй, Мир!», а сервер выводит это сообщение на терминал.
Листинг 14. Сервер
1.#include <sys/types.h>
2.#include <fcntl.h>
3.#include <stdio.h>
91
Листинг 14. Сервер
4.#include <sys/stat.h>
5.#define FIFO “fifo.1”
6.#define MAXBUFF 80
7.
8.int main (void){
9.int fd, n;
10.char buff[MAXBUFF]; /*буфер для чтения данных */
11./*Создадим специальный файл FIFO */
12.if (mknod(FIFO, S_IFIFO | 0666, 0) < 0){
13.printf(“Невозможно создать FIFO\n”);
14.exit(1);
15.}
16./*Получим доступ к FIFO*/
17.if ((fd = open(FIFO, O_RDONLY)) < 0){
18.printf(“Невозможно открыть FIFO\n”);
19.exit(1);
20.}
21./*Прочитаем сообщение (“Здравствуй, Мир!”) */
22. /* и выведем его на экран |
*/ |
23.while ((n = read(fd, buff, MAXBUFF)) > 0)
24.if (write(1, buff, n) != n){
25.printf(“Ошибка вывода\n”);
26.exit(1);
27.}
28./* 3акроем FIFO, и удалим файл */
29.close(fd);
30.if (unlink(FIFO) < 0){
31.printf(“Невозможно удалить FIFO\n”); exit(1);
32.}
33.exit(0);
34.}
Листинг 15. Клиент
1.#include <sys/types.h>
2.#include <sys/stat.h>
3.#include <stdio.h>
4.#include <fcntl.h>
5.
6./*Соглашение об имени FIFO*/
7.#define FIFO “fifo.1”
8.
9.int main (void){
10.int fd, n;
11./*Получим доступ к FIFO*/
92
Листинг 15. Клиент
12.if ((fd = open(FIFO, O_WRONLY)) < 0){
13.printf(“Невозможно открыть FIFO\n”);
14.exit(1);
15.}
16./*Передадим сообщение серверу FIFO*/
17.if (write(fd, “Здравствуй, Мир!\n\0”, 18) != 18){
18.printf(“Ошибка записи\n”); exit(1);
19.}
20.close(fd);
21.exit (0);
22.}
6.3.3. Сигналы
Аналогом программных прерываний в UNIX-подобных операционных системах (в том числе Linux) служат сигналы.
Сигнал – это способ взаимодействия программ, позволяющий сообщать о наступлении определённых событий, например, о появлении в очереди управляющих символов или возникновении ошибки во время работы программы (например, Segmentation Fault – выход за границы памяти).
Как и аппаратное прерывание, сигналы описываются номерами, которые описаны в заголовочном файле signal.h. Кроме цифрового кода, каждый сигнал имеет соответствующее символьное обозначение, например SIGINT.
Большинство типов сигналов предназначены для использования ядром операционной системы, хотя есть несколько сигналов, которые посылаются от процесса к процессу. Полный список доступных сигналов приведён в электронном справочнике man (man 7 signal). Приведём некоторые из них:
SIGABRT – сигнал прерывания процесса (англ. process abort signal). Посылается процессу при вызове им функции abort. В результате сигнала SIGABRT произойдёт аварийное завершение
(англ. abnormal termination) и запись образа памяти (англ. core dump, иногда переводится как «дамп памяти»). Образ памяти процесса сохраняется в файле на диске для изучения с помощью отладчика;
SIGALRM – сигнал таймера (англ. alarm clock). Посылается процессу ядром при срабатывании таймера. Каждый процесс может устанавливать не менее трёх таймеров. Первый из них измеряет прошедшее реальное время. Этот таймер устанавливается самим про-
цессом при помощи системных вызовов alarm или setitimer (см. ниже);
SIGILL – недопустимая команда процессора (англ. illegal instruction). Посылается операционной системой, если процесс пытается выполнить недопустимую машинную команду. Иногда этот сигнал может возникнуть из-за того, что программа каким-либо об-
93
разом повредила свой код. В результате сигнала SIGILL происходит аварийное завершение программы;
SIGINT – сигнал прерывания программы (англ. interrupt). Посылается ядром всем процессам, связанным с терминалом, когда пользователь нажимает клавишу прерывания (т.е., другими словами, в потоке ввода появляется управляющий символ, соответствующий клавише прерывания). Примером клавиши прерывания может служить комбинация Ctrl+C. Это также обычный способ остановки выполняющейся программы;
SIGKILL – сигнал уничтожения процесса (англ. kill). Это довольно специфический сигнал, который посылается от одного процесса к другому и приводит к немедленному прекращению работы получающего сигнал процесса. Иногда он также посылается системой (например, при завершении работы системы). Сигнал SIGKILL – один из двух сигналов, которые не могут игнорироваться или перехватываться (то есть обрабатываться при помощи определённой пользователем процедуры);
SIGPROF – сигнал профилирующего таймера (англ. profiling time expired). Как было уже упомянуто для сигнала SIGALARM, любой процесс может установить не менее трёх таймеров. Второй из этих таймеров может использоваться для измерения времени выполнения процесса в пользовательском и системном режимах. Сигнал SIGPROF генерируется, когда истекает время, установленное в этом таймере, и поэтому может быть использован средством профилирования (планирования работы) программы;
SIGQUIT – сигнал о выходе (англ. quit). Очень похож на сигнал SIGINT. Этот сигнал посылается ядром, когда пользователь нажимает клавишу выхода в используемом терминале. Значение клавиши выхода по умолчанию соответствует символу ASCII F6 или Ctrl+Q. B отличие от SIGINT, этот сигнал приводит к аварийному завершению и сбросу образа памяти;
SIGSEGV – обращение к некорректному адресу памяти
(англ. invalid memory reference). Сокращение SEGV в названии сиг-
нала означает нарушение границ сегментов памяти (англ. segmentation violation). Сигнал генерируется, если процесс пытается обратиться к неверному адресу памяти. Получение сигнала SIGSEGV приводит к аварийному завершению процесса;
SIGTERM – программный сигнал завершения (англ. software termination signal). Используется для завершения процесса. Программист может использовать этот сигнал для того, чтобы дать процессу время для «наведения порядка», прежде чем посылать ему сигнал SIGKILL. Команда kill по умолчанию посылает именно этот сигнал;
SIGWINCH – сигнал, генерируемый драйвером терминала при изменении размеров окна;
94
SIGUSR1 и SIGUSR2 – пользовательские сигналы (англ. user defined signals 1 and 2). Так же, как и сигнал SIGTERM, эти сигналы никогда не посылаются ядром и могут использоваться для любых целей по выбору пользователя.
При получении сигнала процесс может выполнить одно из трёх действий:
выполнить действие по умолчанию. Обычно действие по умолчанию заключается в прекращении выполнения процесса. Для некоторых сигналов, например, для сигналов SIGUSR1 и SIGUSR2, действие по умолчанию заключается в игнорировании сигнала. Для других сигналов, например, для сигнала SIGSTOP, действие по умолчанию заключается в остановке процесса;
игнорировать сигнал и продолжать выполнение. В больших программах неожиданно возникающие сигналы могут привести к проблемам. Например, нет смысла позволять программе останавливаться в результате случайного нажатия на клавишу прерывания, в то время как она производит обновление важной базы данных;
выполнить определённое пользователем действие. Программист может задать собственный обработчик сигнала. Например, выполнить при выходе из программы операции по «наведению порядка» (такие как удаление рабочих файлов), что бы ни являлось причиной
этого выхода.
Чтобы определить действие, которое необходимо выполнить при получении сигнала, используется системный вызов signal:
#include <signal.h>
typedef void (*sighandler_t) (int);
sighandler_t signal (int signum, sighandler_t handler);
Вызов signal определяет действие программы при поступлении сигнала с номером signum. Действие может быть задано как адрес пользовательской функции (в таком случае в функцию в качестве аргумента передаётся номер полученного сигнала) или как макросы SIG_IGN (для игнорирования сигнала) и SIG_DFL (для использования обработчика по умолчанию).
Если действие определено как пользовательская функция, то при поступлении сигнала программа будет прервана, и процессор начнёт выполнять указанную функцию. После её завершения выполнение программы, получившей сигнал, будет продолжено, и обработчик сигнала будет установлен с помощью SIG_DFL.
Чтобы вызвать сигнал, используется системный вызов raise:
95