как нельзя лучше подходят приложениям для извлечения и хранения данных в файле, все же они не всегда являются самыми быстрыми методами. Они допускают управление отдельными порциями данных; для записи же нескольких порций данных требуется несколько системных вызовов. Подобным образом, если приложению необходим доступ к данным в разных частях файла, оно должно вызывать
lseek
между каждым
read
или
write
, удваивая количество необходимых системных вызовов. Для улучшения эффективности существуют другие системные вызовы.
13.4.1. Разбросанное/сборное чтение и запись
Приложениям часто требуется читать
и записывать данные различных типов в последовательные области файла. Несмотря на то что это можно делать сравнительно легко с помощью множества вызовов
read
и
write
, такое решение не является особо эффективным. Вместо этого приложения могут перемещать все данные в последовательную область памяти, делая возможным один системный вызов. Однако эти действия приводят к множеству ненужных операций с памятью.
Linux предлагает системные вызовы
readv
и
writev
, реализующие разбросанное/сборное чтение и запись [98] . В отличие от стандартных элементов своего уровня, получающих по одному указателю и размеру буфера, эти системные вызовы получают массивы записей, каждая запись которых описывает буфер. Буферы читаются или записываются в том порядке, в каком они приведены в массиве. Каждый буфер описывается с помощью структуры
struct iovec
.
98
Они так именуются, поскольку чтение разбрасывает данные по всей памяти, а запись собирает данные из разных областей памяти. Они также известны как векторное чтение и запись. Этим объясняется наличие "v" в конце
readv
и
writev
.
#include <sys/uio.h>
struct iovec {
void * iov_base; /* адрес буфера */
size_t iov_len; /* длина буфера */
};
Первый элемент,
iov_base
, указывает на буферное пространство. Элемент
iov_len
— это количество символов в буфере. Эти элементы представляют собой то же, что и второй и третий параметры, передаваемые
read
и
write
.
Ниже показаны прототипы
readv
и
writev
.
#include <sys/uio.h>
int readv(int fd, const struct iovec * vector, size_t count);
int writev(int fd, const struct iovec * vector, size_t count);
Первый аргумент является файловым дескриптором, с которого можно считывать или на который можно записывать. Второй аргумент,
vector
, указывает на массив элементов
count struct iovec
. Обе функции возвращают общее количество прочитанных или записанных байтов.
Ниже приведена простая программа-пример, использующая
writev
для отображения простого сообщения на стандартном устройстве вывода.
1: /* gather.с */
2:
3: #include <sys/uio.h>
4:
5: int main(void) {
6: struct iovec buffers[3];
7:
8: buffers[0].iov_base = "hello";
9: buffers[0].iov_len = 5;
10:
11: buffers[1].iov_base = " ";
12: buffers[1].iov_len = 1;
13:
14: buffers[2].iov_base = "world\n";
15: buffers[2].iov_len = 6;
16:
17: writev(1, buffers, 3);
18:
19: return 0;
20: }
13.4.2.
Игнорирование указателя файла
Программы, использующие бинарные файлы, часто выглядят, как показано ниже.
lseek(fd, SEEK_SET, offset1);
read(fd, buffer, bufferSize);
offset2 = someOperation(buffer);
lseek(fd, SEEK_SET, offset2);
read(fd, buffer2, bufferSize2);
offset3 = someOperation(buffer2);
lseek(fd, SEEK_SET, offset3);
read(fd, buffer3, bufferSize3);
Необходимость поиска нового расположения с помощью
lseek
перед каждым read удваивает количество системных вызовов, поскольку указатель файла никогда не располагается правильно после read из-за непоследовательной природы хранения данных в файле. Существуют альтернативы
read
и
write
, принимающие смещение файла в качестве параметра, и ни одна из альтернатив не использует указатель файла, чтобы выяснить, к какой части файла можно получить доступ, или какую его часть можно обновить. Обе функции работают только применительно к просматриваемым файлам, поскольку непросматриваемые файлы можно читать или записывать только в текущем расположении.
. offset определяет, с какой точки файла следует читать, а в какую — записывать. Как и их "тезки", эти функции возвращают количество переданных байтов. Ниже приведена версия
Эта эмулированная версия в большинстве случаев ведет себя корректно, но действует не так, как фактический системный вызов, если сигналы принимаются во время его выполнения.
int pread (int fd, void * data, int size, int offset) {
int oldOffset;
int rc;
int oldErrno;
/* переместить указатель файла в новое расположение */
oldOffset = lseek(fd, SEEK_SET, offset);
if (oldOffset < 0) return -1;
rc = read(fd, data, size);
/* восстановить указатель файла, предварительно сохранив errno */
oldErrno = errno;
lseek(fd, SEEK_SET, oldOffset);
errno = oldErrno;
return rc;
}
Глава 14
Операции с каталогами
Как и во многих других операционных системах, для организации файлов в Linux используются каталоги. Каталоги (представляющие собой особые типы файлов, которые содержат списки имен файлов) состоят из файлов, а также других каталогов, образуя иерархию файлов. Все системы Linux содержат корневой каталог, известный как
/
, через который (прямо или непрямо) можно получить доступ ко всем файлам системы.