Марк Митчелл - Программирование для Linux. Профессиональный подход
- Название:Программирование для Linux. Профессиональный подход
- Автор:
- Жанр:
- Издательство:Вильямс
- Год:2002
- Город:Москва
- ISBN:5-8459-0243-6
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Марк Митчелл - Программирование для Linux. Профессиональный подход краткое содержание
Данная книга в основном посвящена программированию в среде GNU/Linux. Авторы применяют обучающий подход, последовательно излагая самые важные концепции и методики использования расширенных возможностей системы GNU/Linux в прикладных программах. Читатели научатся писать программы, к интерфейсу которых привыкли пользователи Linux; освоят такие технологии, как многозадачность, многопотоковое программирование, межзадачное взаимодействие и взаимодействие с аппаратными устройствами; смогут улучшить свои программы, сделав их быстрее, надежнее и безопаснее; поймут особенности системы GNU/Linux, ее ограничения, дополнительные возможности и специфические соглашения.
Книга предназначена для программистов, уже знакомых с языком С и имеющих базовый опыт работы в GNU/Linux.
Программирование для Linux. Профессиональный подход - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
Некоторые программы формируют в отображаемом файле структуры данных. При каждом следующем запуске программа повторно инициализирует файл в памяти, вследствие чего восстанавливается начальное состояние структур. В подобной ситуации следует помнить о том, что указатели на структуры будут некорректными, если они не локализованы в пределах одной отображаемой области и если файл не загружается по одному и тому же адресу.
Другой удобный прием — отображение в памяти файла /dev/zero
(описывается в разделе 6.5.2, "/dev/zero"). Этот файл ведет себя так, как будто содержит бесконечное число нулевых байтов. Операции записи в него игнорируются. Описываемый прием часто применяется в пользовательских функциях выделения памяти, которым необходимо инициализировать блоки памяти.
5.4. Каналы
Канал — это коммуникационное устройство, допускающее однонаправленное взаимодействие. Данные, записываемые на "входном" конце канала, читаются на "выходном" его конце. Каналы являются последовательными устройствами: данные всегда читаются в том порядке, в котором они были записаны. Канал обычно используется как средство связи между двумя потоками одного процесса или между родительским и дочерним процессами.
В интерпретаторе команд канал создается оператором |
. Например, показанная ниже команда заставляет интерпретатор запустить два дочерних процесса, один — для программы ls
, а второй — для программы less
:
% ls | less
Интерпретатор также формирует канал, соединяющий стандартный выходной поток подпроцесса ls
со стандартным входным потоком подпроцесса less
. Таким образом, имена файлов, перечисляемые программой ls
, посылаются программе постраничной разбивки less
в том порядке, в котором они отображались бы нетерминале.
Информационная емкость канала ограничена. Если пишущий процесс помещает данные в канал быстрее, чем читающий процесс их извлекает, и буфер канала переполняется, то пишущий процесс блокируется до тех пор, пока буфер не освободится. И наоборот: если читающий процесс обращается к каналу, в который еще не успели поступить данные, он блокируется в ожидании данных. Таким образом, канал автоматически синхронизирует оба процесса.
5.4.1. Создание каналов
Канал создается с помощью функции pipe()
. Ей необходимо передать массив из двух целых чисел. В элементе с индексом 0 функция сохраняет дескриптор файла, соответствующего выходному концу канала, а в элементе с индексом 1 сохраняется дескриптор файла, соответствующего входному концу канала. Рассмотрим следующий фрагмент программы
int pipe_fds[2];
int read_fd;
int write_fd;
pipe(pipe_fds);
read_fd = pipe_fds[0];
write_fd = pipe_fds[1];
Данные, записываемые в файл write_fd
, могут быть прочитаны из файла read_fd
.
5.4.2. Взаимодействие родительского и дочернего процессов
Функция pipe()
создает два файловых дескриптора, которые действительны только в текущем процессе и его потомках. Эти дескрипторы нельзя передать постороннему процессу. Дочерний процесс получает копии дескрипторов после завершения функции fork()
.
В программе, показанной в листинге 5.7. родительский процесс записывает в канал строку, а дочерний процесс читает ее. С помощью функции fdopen()
файловые дескрипторы приводятся к типу FILE*
. Благодаря этому появляется возможность использовать высокоуровневые функции ввода-вывода, такие как printf()
и fgets()
.
#include
#include
#include
/* Запись указанного числа копий (COUNT) сообщения (MESSAGE)
в поток (STREAM) с паузой между каждой операцией. */
void writer(const char* message, int count, FILE* stream) {
for (; count > 0; --count) {
/* Запись сообщения в поток с немедленным "выталкиванием"
из буфера. */
fprintf(stream, "%s\n", message);
fflush(stream);
/* Небольшая пауза. */
sleep(1);
}
/* Чтение строк из потока, пока он не опустеет. */
void reader(FILE* stream) {
char buffer[1024];
/* Чтение данных, пока не будет обнаружен конец потока.
Функция fgets() завершается, когда встречает символ
новой строки или признак конца файла. */
while (!feof(stream)
&& !ferror(stream)
&& fgets(buffer, sizeof (buffer), stream) != NULL)
fputs(buffer, stdout);
}
int main() {
int fds[2];
pid_t pid;
/* Создание канала. Дескрипторы обоих концов канала
помещаются в массив FDS. */
pipe(fds);
/* порождение дочернего процесса. */
pid = fork();
if (pid == (pid_t)0) {
FILE* stream;
/* Это дочерний процесс. Закрываем копию входного конца
канала. */
close(fds[1]);
/* Приводим дескриптор выходного конца канала к типу FILE*
и читаем данные из канала. */
stream = fdopen(fds[0], "r");
reader(stream);
close(fds[0]);
} else {
/* Это родительский процесс. */
FILE* stream;
/* Закрываем копию выходного конца канала. */
close(fds[0]);
/* Приводим дескриптор входного конца канала к типу FILE*
и записываем данные в канал. */
stream = fdopen(fds[1], "w");
writer("Hello, world.", 5, stream);
close(fds[1]);
}
return 0;
}
Сначала в программе объявляется массив fds
, состоящий из двух целых чисел. Функция pipe()
создает канал и помещает в массив дескрипторы входного и выходного концов канала. Затем функция fork()
порождает дочерний процесс. После закрытия выходного конца канала родительский процесс начинает записывать строки в канал. Дочерний процесс читает строки из канала, предварительно закрыв его входной конец.
Обратите внимание на то. что в функции writer()
родительский процесс принудительно "выталкивает" буфер канала, вызывая функцию fflush()
. Без этого строка могла бы ""застрять" в буфере и отправиться в канал только после завершения родительского процесса.
При вызове команды ls | less
функция fork ()
выполняется дважды: один раз — для дочернего процесса ls
, второй раз — для дочернего процесса less
. Оба процесса наследуют копии дескрипторов канала, поэтому могут общаться друг с другом. О соединении несвязанных процессов речь пойдет ниже, в разделе 5.4.5, "Каналы FIFO".
5.4.3. Перенаправление стандартных потоков ввода, вывода и ошибок
Часто требуется создать дочерний процесс и сделать один из концов канала его стандартным входным или выходным потоком. В этом случае на помощь приходит функция dup2()
, которая делает один файловый дескриптор равным другому. Вот как, например, можно связать стандартный входной поток с файлом fd
:
Интервал:
Закладка: