Linux программирование в примерах - Роббинс Арнольд
Как упоминалось, число открытых файлов, если оно большое, ограничивается, и вам всегда следует закрывать файлы, когда работа с ними закончена. Если вы этого не сделаете, то в конечном счете выйдете за пределы лимита дескрипторов файлов, создав ситуацию, которая ведет к потере устойчивости части вашей программы.
Система закрывает все открытые файлы, когда процесс завершается, но — за исключением 0, 1 и 2 — плохая манера полагаться на это.
Когда open() возвращает новый дескриптор файла, она всегда возвращает наименьшее неиспользуемое целое значение. Всегда. Поэтому, если открыты дескрипторы файлов 0–6 и программа закрывает дескриптор файла 5, следующий вызов open() вернет 5, а не 7. Это поведение важно; далее в книге мы увидим, как оно используется для аккуратной реализации многих важных особенностей Unix, таких, как перенаправление ввода/вывода и конвейеризация (piping)
4.4.2.1. Отображение переменных FILE* на дескрипторы файлов
Стандартные библиотечные функции ввода/вывода и переменные FILE* из <stdio.h>, такие, как stdin, stdout и stderr, построены поверх основанных на дескрипторах файлов системных вызовах.
Иногда полезно получить непосредственный доступ к дескриптору файла, связанному с указателем файла <stdio.h>, если вам нужно сделать что-либо, не определенное стандартом С ISO. Функция fileno() возвращает лежащий в основе дескриптор файла:
#include <stdio.h> /* POSIX */
int fileno(FILE *stream);
Пример мы увидим позже, в разделе 4.4.4. «Пример: Unix cat».
4.4.2.2. Закрытие всех открытых файлов
Открытые файлы наследуются порожденными процессами от своих родительских процессов. Фактически они являются общими. В частности, общим является положение в файле. Подробности мы оставим для дальнейшего обсуждения в разделе 9.1.1.2 «Разделение дескрипторов файлов».
Поскольку программы могут наследовать другие файлы, иногда вы можете увидеть программы, которые закрывают все свои файлы, чтобы начать с «чистого состояния» В частности, типичен код наподобие этого:
int i;
/* оставить лишь 0, 1, и 2 */
for (i = 3; i < getdtablesize(); i++)
(void)close(i);
Предположим, что результат getdtablesize() равен 1024. Этот код работает, но он делает (1024-3)*2 = 2042 системных вызова. 1020 из них не нужны, поскольку возвращаемое значение getdtablesize() не изменяется. Вот лучший вариант этого кода:
int i, fds;
for (i = 3, fds = getdtablesize(); i < fds; i++)
(void)close(i);
Такая оптимизация не ухудшает читаемость кода, но может быть заметна разница, особенно на медленных системах. В общем, стоит поискать случаи, когда в циклах повторно вычисляется один и тот же результат, чтобы посмотреть, нельзя ли вынести вычисление за пределы цикла. Хотя в таких случаях нужно убедиться, что вы (а) сохраняете правильность кода и (б) сохраняете его читаемость!
4.4.3. Чтение и запись
Ввод/вывод осуществляется системными вызовами read() и write() соответственно:
#include <sys/types.h> /* POSIX */
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
Каждая функция сделана как можно проще. Аргументами являются дескриптор открытого файла, указатель на буфер для чтения или записи данных и число читаемых или записываемых байтов.
Возвращаемое значение является числом действительно прочитанных или записанных байтов. (Это число может быть меньше запрошенного: при операции чтения это происходит, когда в файле осталось меньше count байтов, а при операции записи это случается, когда диск заполнен или произошла еще какая-нибудь ошибка.) Возвращаемое значение -1 означает возникшую ошибку, в этом случае errno указывает эту ошибку. Когда read() возвращает 0, это означает, что достигнут конец файла.
Теперь мы можем показать оставшуюся часть кода для ch04-cat. Процедура process() использует 0 для стандартного ввода, если именем файла является «-» (строки 50 и 51). В противном случае она открывает данный файл:
36 /*
37 * process --- сделать что-то с файлом, в данном случае,
38 * послать его в stdout (fd 1).
39 * Возвращает 0, если все нормально; в противном случае 1.
40 */
41
42 int
43 process(char *file)
44 {
45 int fd;
46 ssize_t rcount, wcount;
47 char buffer[BUFSIZ];
48 int errors = 0;
49
50 if (strcmp(file, "-") == 0)
51 fd = 0;
52 else if ((fd = open(file, O_RDONLY)) < 0) {
53 fprintf(stderr, "%s: %s: cannot open for reading: %sn",
54 myname, file, strerror(errno));
55 return 1;
56 }
Буфер buffer (строка 47) имеет размер BUFSIZ; эта константа определена В <stdio.h> как «оптимальный» размер блока для ввода/вывода. Хотя значение BUFSIZ различается в разных системах, код, использующий эту константу, чистый и переносимый.
Основой процедуры является следующий цикл, который повторно читает данные до тех пор, пока не будет достигнут конец файла или не возникнет ошибка.
Откройте для себя мир чтения на siteknig.com - месте, где каждая книга оживает прямо в браузере. Здесь вас уже ждёт произведение Linux программирование в примерах - Роббинс Арнольд, относящееся к жанру Интернет. Никаких регистраций, никаких преград - только вы и история, доступная в полном формате. Наш литературный портал создан для тех, кто любит комфорт: хотите читать с телефона - пожалуйста; предпочитаете ноутбук - идеально! Все книги открываются моментально и представлены полностью, без сокращений и скрытых страниц. Каталог жанров поможет вам быстро найти что-то по настроению: увлекательный роман, динамичное фэнтези, глубокую классику или лёгкое чтение перед сном. Мы ежедневно расширяем библиотеку, добавляя новые произведения, чтобы вам всегда было что открыть "на потом". Сегодня на siteknig.com доступно более 200000 книг - и каждая готова стать вашей новой любимой. Просто выбирайте, открывайте и наслаждайтесь чтением там, где вам удобно.


