ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);
Возвращает количество записанных байтов или –1 при ошибке
Вызов pread() эквивалентен атомарному выполнению следующих вызовов:
off_t orig;
orig = lseek(fd, 0, SEEK_CUR); /* Сохранение текущего смещения */
lseek(fd, offset, SEEK_SET);
s = read(fd, buf, len);
lseek(fd, orig, SEEK_SET); /* Восстановление исходного файлового смещения */
Как для pread(), так и для pwrite() файл, ссылка на который дается в аргументе fd, должен быть пригодным для изменения смещения (то есть представлен файловым дескриптором, в отношении которого допустимо вызвать lseek()).
В частности, такие системные вызовы могут пригодиться в многопоточных приложениях. В главе 29 будет показано, что все потоки в процессе совместно используют одну и ту же таблицу файловых дескрипторов. Это означает, что файловое смещение для каждого открытого файла является для всех потоков глобальным. Используя pread() или pwrite(), несколько потоков могут одновременно осуществлять ввод-вывод в отношении одного и того же файлового дескриптора, без влияния тех изменений, которые производят в отношении файлового смещения другие потоки. Если попытаться воспользоваться вместо этого lseek() плюс read() (или write()), то мы создадим состояние гонки, подобной одной из тех, описание которых давалось при рассмотрении флага O_APPEND в разделе 5.1. (Системные вызовы pread() и pwrite() могут также пригодиться для устранения состояния гонки в приложениях, когда у нескольких процессов имеются файловые дескрипторы, ссылающиеся на одну и ту же дескрипцию открытого файла.)
При условии многократного выполнения вызовов lseek() с последующим файловым вводом-выводом системные вызовы pread() и pwrite() могут также предложить в некоторых случаях преимущества в производительности. Дело в том, что отдельный системный вызов pread() (или pwrite()) приводит к меньшим издержкам, чем два системных вызова: lseek() и read() (или write()). Но издержки, связанные с системными вызовами, обычно незначительны по сравнению со временем фактического выполнения ввода-вывода.
Системные вызовы readv() и writev() выполняют фрагментированный ввод/вывод (scatter-gather I/O).
#include <sys/uio.h>
ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
Возвращает количество считанных байтов, 0 при EOF или –1 при ошибке
ssize_t writev(int fd, const struct iovec *iov, int iovcnt);
Возвращает количество записанных байтов или –1 при ошибке
За один системный вызов обрабатываются несколько таких буферов данных. Набор передаваемых буферов определяется массивом iov. Количество элементов в iov указывается в iovcnt. Каждый элемент в iov является структурой с такой формой:
struct iovec {
void *iov_base; /* Начальный адрес буфера */
size_t iov_len; /* Количество байтов для передачи в буфер или из него */
};
Согласно спецификации SUSv3, допускается устанавливать ограничение по количеству элементов в iov. Реализация может уведомить о своем ограничении, определив значение IOV_MAX в заголовочном файле <limits.h> или в ходе выполнения через возвращаемое значение вызова sysconf(_SC_IOV_MAX). (Вызов sysconf() рассматривается в разделе 11.2.) В спецификации SUSv3 требуется, чтобы это ограничение было не меньше 16. В Linux для IOV_MAX определено значение 1024, что соответствует ограничениям ядра на размер этого вектора (задается константой ядра UIO_MAXIOV).
При этом функции оболочки из библиотеки glibc для readv() и writev() незаметно выполняют дополнительные действия. Если системный вызов дает сбой по причине слишком большого значения iovcnt, функция-оболочка временно выделяет один буфер, чьего объема достаточно для хранения всех элементов, описанных iov, и выполняет вызов read() или write() (см. далее тему о возможной реализации writev() с использованием write()).
На рис. 5.3 показан пример взаимосвязанности аргументов iov и iovcnt, а также буферов, на которые они ссылаются.
Фрагментированный ввод
Системный вызов readv() выполняет фрагментированный ввод: он считывает непрерывную последовательность байтов из файла, ссылка на который дается в файловом дескрипторе fd, и помещает («фрагментирует») эти байты в буферы, указанные аргументом iov. Каждый из буферов, начиная с того, что определен элементом iov[0], полностью заполняется, прежде чем readv() переходит к следующему буферу.
Рис. 5.3. Пример массива iov и связанных с ним буферов
Важным свойством readv() является выполнение всей работы в атомарном режиме, то есть с позиции вызывающего процесса ядро совершает единое портирование данных между файлом, на который указывает fd, и пользовательской памятью. Это означает, к примеру, что при чтении из файла можно быть уверенными, что диапазон считываемых байтов непрерывен, даже если другой процесс (или поток), совместно используя то же файловое смещение, предпринимает попытку манипулировать смещением в то время, когда выполняется системный вызов readv().