Чтение онлайн

ЖАНРЫ

Разработка приложений в среде Linux. Второе издание

Троан Эрик В.

Шрифт:

Несмотря на то что системные вызовы

read
и
write
как нельзя лучше подходят приложениям для извлечения и хранения данных в файле, все же они не всегда являются самыми быстрыми методами. Они допускают управление отдельными порциями данных; для записи же нескольких порций данных требуется несколько системных вызовов. Подобным образом, если приложению необходим доступ к данным в разных частях файла, оно должно вызывать
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
, принимающие смещение файла в качестве параметра, и ни одна из альтернатив не использует указатель файла, чтобы выяснить, к какой части файла можно получить доступ, или какую его часть можно обновить. Обе функции работают только применительно к просматриваемым файлам, поскольку непросматриваемые файлы можно читать или записывать только в текущем расположении.

#define XOPEN_SOURCE 500

#include <unistd.h>

size_tpread(int fd, void * buf, size_t count, off_t offset);

size_t pwrite(int fd, void * buf, size_t count, off_t offset);

#endif

Это выглядит подобно прототипам

read
и
write
с четвертым параметром,
offset
. offset определяет, с какой точки файла следует читать, а в какую — записывать. Как и их "тезки", эти функции возвращают количество переданных байтов. Ниже приведена версия
pread
, реализованная с помощью
read
и
lseek
, что облегчает понимание ее функции [99] .

99

Эта эмулированная версия в большинстве случаев ведет себя корректно, но действует не так, как фактический системный вызов, если сигналы принимаются во время его выполнения.

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 содержат корневой каталог, известный как

/
, через который (прямо или непрямо) можно получить доступ ко всем файлам системы.

Поделиться с друзьями: