Категории
Самые читаемые
PochitayKnigi » Компьютеры и Интернет » Программное обеспечение » Linux программирование в примерах - Арнольд Роббинс

Linux программирование в примерах - Арнольд Роббинс

Читать онлайн Linux программирование в примерах - Арнольд Роббинс

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 75 76 77 78 79 80 81 82 83 ... 165
Перейти на страницу:

Если вытирающий супруг оказывается быстрее моющего, сушилка становится пустой, и вытирающему приходится ждать, пока не будут готовы новые тарелки. Напротив, если быстрее вытирающий супруг, сушилка наполняется, и моющему приходится ждать, пока она не опустеет, прежде чем помещать в нее тарелки. Это изображено на рис. 9.3.

Рис. 9.3. Синхронизация процессов канала

9.3.2. Очереди FIFO

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

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

Для решения этой проблемы System III предложила идею о FIFO. FIFO,[97] или именованный канал, является файлом в файловой системе, который действует подобно каналу. Другими словами, один процесс открывает FIFO для записи, тогда как другой открывает его для чтения. Затем данные, записанные; в FIFO, читаются читателем. Данные буферируются ядром, а не хранятся на диске.

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

Функция mkfifo() создает файлы FIFO:

#include <sys/types.h> /* POSIX */

#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);

Аргумент pathname является именем создаваемого FIFO, a mode является данными ему правами доступа, аналогичными второму аргументу функции creat() или третьему аргументу функции open() (см. раздел 4.6 «Создание файлов»). Файлы FIFO удаляются, как любые другие, с помощью remove() или unlink() (см. раздел 5.1.5.1 «Удаление открытых файлов»).

Справочная страница GNU/Linux mkfifo(3) указывает, что FIFO должен быть открыт как для чтения, так и для записи в одно и то же время, до того, как может быть осуществлен ввод/вывод: «Открытие FIFO для чтения обычно блокирует до тех пор, пока какой-нибудь другой процесс не откроет тот же FIFO для записи, и наоборот». После открытия файла FIFO он действует подобно обычному каналу; т.е. это просто еще один дескриптор файла.

Команда mkfifo доставляет этот системный вызов на командный уровень. Это упрощает показ файла FIFO в действии:

$ mkfifo afifo /* Создание файла FIFO */

$ ls -l afifo

 /* Показать тип и права доступа, обратите внимание на 'p' впереди */

prw-r--r-- 1 arnold devel 0 Oct 23 15:49 afifo

$ cat < afifo & /* Запустить читателя в фоновом режиме */

[1] 22100

$ echo It was a Blustery Day > afifo /* Послать данные в FIFO */

$ It was a Blustery Day /* Приглашение оболочки, cat выводит данные */

 /* Нажмите ENTER, чтобы увидеть статус завершения задания */

[1]+ Done cat <afifo /* cat завершился */

9.4. Управление дескрипторами файлов

На данный момент части загадки почти полностью составлены, fork() и exec() создают процессы и запускают в них программы, pipe() создает канал, который может использоваться для IPC. Чего до сих пор не хватает, так это способа помещения дескрипторов канала на место стандартных ввода и вывода для производителя и потребителя канала.

Системные вызовы dup() и dup2(), совместно с close() дают вам возможность поместить (скопировать) открытый дескриптор файла на другой номер. Системный вызов fcntl() дает вам возможность то же самое и управлять несколькими важными атрибутами открытых файлов.

9.4.1. Дублирование открытых файлов: dup() и dup2()

Два системных вызова создают копию открытого дескриптора файла:

#include <unistd.h> /* POSIX */

int dup(int oldfd);

int dup2(int oldfd, int newfd);

Функции следующие:

int dup(int oldfd)

Возвращает наименьшее значение неиспользуемого дескриптора файла; это копия oldfd. dup() возвращает неотрицательное целое в случае успеха и -1 при неудаче.

int dup2(int oldfd, int newfd)

Делает newfd копией oldfd; если newfd открыт, он сначала закрывается, как при использовании close(). dup2() возвращает новый дескриптор или -1, если была проблема. Помните рис. 9.1, в котором два процесса разделяли общие указатели на один и тот же элемент файла в таблице файлов ядра? dup() и dup2() создают ту же ситуацию внутри одного процесса. См. рис. 9.4.

Рис. 9.4. Разделение дескриптора файла как результат 'dup2(1, 3)'

На этом рисунке процесс выполнил 'dup2(1, 3)', чтобы сделать дескриптор файла 3-й копией стандартного вывода, дескриптора файла 1. Точно как описано ранее, эти два дескриптора разделяют общее смещение открытого файла.

В разделе 4.4.2 «Открытие и закрытие файлов» мы упомянули, что open() (и creat()) всегда возвращают наименьшее целое значение неиспользуемого дескриптора для открываемого файла. Этому правилу следуют почти все системные вызовы, которые возвращают новые дескрипторы файлов, а не только open() и creat(). (dup2() является исключением, поскольку он предусматривает способ получения конкретного нового дескриптора файла, даже если он не является наименьшим неиспользуемым дескриптором.)

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

1. Создать канал с помощью pipe(). Это должно быть сделано сначала, чтобы два порожденных процесса могли унаследовать дескрипторы открытых файлов.

2. Создать то, что мы называем «левым потомком». Это процесс, стандартный вывод которого идет в канал. В данном процессе сделать следующее:

 a. Использовать 'close(pipefd[0])', поскольку читаемый конец канала в левом потомке не нужен.

 b. Использовать 'close(1)', чтобы закрыть первоначальный стандартный вывод.

 c. Использовать 'dup(pipefd[1])' для копирования записываемого конца канала в дескриптор файла 1.

 d. Использовать 'close(pipefd[1])', поскольку нам не нужны две копии открытого дескриптора.

 e. Выполнить exec для запускаемой программы.

3. Создать то, что мы называем «правым потомком». Это процесс, стандартный ввод которого поступает из канала. Шаги для этого потомка являются зеркальным отражением шагов для левого потомка:

 a. Использовать 'close(pipefd[1])', поскольку записываемый конец канала в правом потомке не нужен.

 b. Использовать 'close(0)', чтобы закрыть первоначальный стандартный ввод.

 c. Использовать 'dup(pipefd[0])' для копирования читаемого конца канала в дескриптор файла 0.

 d. Использовать 'close(pipefd[0])', поскольку нам не нужны две копии открытого дескриптора.

 e. Выполнить exec для запускаемой программы.

4. В родителе закрыть оба конца канала — 'close(pipefd[0]); close(pipefd[1])'.

5. Наконец, использовать в родителе wait() для ожидания завершения обоих порожденных процессов.

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

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

1 ... 75 76 77 78 79 80 81 82 83 ... 165
Перейти на страницу:
Тут вы можете бесплатно читать книгу Linux программирование в примерах - Арнольд Роббинс.
Комментарии