Как внутренне работает list / I writeV? - PullRequest
8 голосов
/ 18 февраля 2012

Функция writev принимает массив struct iovec в качестве входного аргумента

writev(int fd, const struct iovec *iov, int iovcnt);

Входные данные представляют собой список буферов памяти, которые необходимо записать в файл (скажем). То, что я хочу знать, это:

Внутренне ли writev делает это:

for (each element in iov) write(element)

такой, что каждый элемент iov записывается в файл в отдельном вызове ввода / вывода? Или writev записывает все в файл в одномоментном вызове ввода / вывода?

Ответы [ 3 ]

7 голосов
/ 18 февраля 2012

Согласно стандартам, упомянутый вами цикл for не является допустимой реализацией writev по нескольким причинам:

  1. Цикл может не завершить запись одного iov, прежде чем перейти к следующему, в случае короткой записи - но это можно обойти, сделав цикл более сложным.
  2. Цикл может иметь неправильное поведение в отношении атомарности для каналов: если общая длина записи меньше, чем PIPE_BUF, запись в канал должна быть атомарной, но цикл нарушит требование атомарности. Эту проблему нельзя обойти, кроме как путем перемещения всех записей iov в один буфер перед записью, когда общая длина не превышает PIPE_BUF.
  3. Цикл может иметь случаи, когда он может привести к блокировке, когда для выполнения частичной записи без блокировки потребуется один вызов writev. Насколько я знаю, эту проблему невозможно обойти в общем случае.
  4. Возможно, другие причины, о которых я не задумывался.

Я не уверен насчет пункта № 3, но он определенно существует в противоположном направлении при чтении. Вызов read в цикле может блокироваться, если у терминала есть некоторые данные (короче, чем общая длина IOV), за которыми следует индикатор EOF; вызов readv должен немедленно вернуться с частичным чтением в этом случае. Однако, из-за ошибки в Linux, readv на терминалах фактически реализован как цикл read в пространстве ядра, и он демонстрирует эту блокирующую ошибку. Мне пришлось обойти эту ошибку при реализации stl musl:

http://git.etalabs.net/cgi-bin/gitweb.cgi?p=musl;a=commit;h=2cff36a84f268c09f4c9dc5a1340652c8e298dc0

Чтобы ответить на последнюю часть вашего вопроса:

Или writev записывает все в файл за один вызов ввода-вывода?

Во всех случаях совместимая реализация writev будет представлять собой один системный вызов. Переходя к тому, как это реализовано в Linux: для обычных файлов и для большинства устройств базовый файловый драйвер имеет методы, которые реализуют io в стиле iov напрямую, без какого-либо внутреннего цикла. Но драйвер терминала в Linux сильно устарел и не имеет современных методов ввода-вывода, из-за чего ядро ​​возвращается к циклу записи / чтения для writev / readv при работе на терминале.

5 голосов
/ 07 июля 2012

Прямой способ узнать, как работает код, - прочитать исходный код.

см. http://www.oschina.net/code/explore/glibc-2.9/sysdeps/posix/writev.c

Он просто размещает буфер () или malloc (), копирует в него все векторы и вызывает write () один раз.

Вот как это работает. Ничего загадочного.

3 голосов
/ 24 мая 2012
Or does writev write everything to file in a single I/O call?

Я не во всем, хотя sys_writev старается написать все за один вызов. это зависит от реализации vfs, если vfs не предоставляет реализацию writev, тогда kenerl вызовет vfs 'write () в цикле. лучше проверить возвращаемое значение writev / readv, чтобы увидеть, сколько байт wrotten, как вы делаете в write ().

Вы можете найти код writev в ядре, fs / read_write.c: do_readv_writev.

...