почему именно комбинация read()
/ write()
медленнее?
На странице руководства об этом совершенно ясно. Выполнение read()
, а затем write()
требует копирования данных два раза.
Почему копия существует вообще?
Это должно быть совершенно очевидно : поскольку вы вызываете read
, вы хотите данные, которые будут скопированы в память вашего процесса в указанном целевом буфере. То же самое касается write
: вы хотите данные, которые будут скопированы из памяти вашего процесса. Ядро на самом деле не знает, что вы просто хотите сделать read
+ write
, и этого можно избежать двухкратного копирования туда и обратно.
При выполнении read
данные копируются ядром из файлового дескриптора в память процесса. При выполнении write
данные копируются ядром из памяти процесса в дескриптор файла.
Не удалось ядру просто прочитать из переданного буфера на write()
без предварительного копирования все это в пространство ядра?
Важнейшим моментом здесь является то, что когда вы читаете или записываете файл, файл должен отображаться ядром с диска в память, чтобы его можно было прочитать или написано. Это называется файловый ввод-вывод с отображением в память , и это огромный фактор производительности современных операционных систем.
Содержимое файла уже присутствует в ядре память, отображаемая как страница памяти (или более). В случае read
данные должны быть скопированы со страницы памяти ядра этого файла в память процесса, в то время как в случае write
данные должны быть скопированы из память процесса до страницы памяти ядра файла. Затем ядро обеспечит правильную запись данных на странице (страницах) памяти ядра, соответствующей файлу, при необходимости (, если вообще необходимо).
Теоретически, этого «промежуточного» сопоставления ядра можно было бы избежать, и файл мог бы быть отображен непосредственно в память пространства пользователя, но тогда приложению пришлось бы управлять им вручную, что сложно и легко испортить. Вот почему для обычных файловых операций файлы отображаются в память ядра. Ядро предоставляет высокоуровневые API-интерфейсы для взаимодействия с ними пользовательских программ, и тяжелая работа остается за самим ядром.
Системный вызов sendfile
намного быстрее, поскольку вам не нужно выполнять копирование два раза. , но только один раз. Предполагая, что вы хотите сделать sendfile
из файла A
в файл B
, ядру нужно только скопировать данные из A
в B
. Однако в случае read
+ write
ядру необходимо сначала скопировать из A
в ваш процесс, а затем из вашего процесса в B
. Эта двойная копия, конечно, медленнее, и если вам действительно не нужно читать или манипулировать данными, то это пустая трата времени.
Я кратко рассмотрел код ядра для читать и писать, но не видел копию.
С точки зрения кода ядра весь процесс чтения файла очень сложен, но в итоге ядро делает «специальную» версию. из memcpy()
, называемый copy_to_user()
, который копирует содержимое файла из памяти ядра в память пространства пользователя (делая соответствующие проверки перед выполнением фактической копии). Более конкретно, для файлов используется функция copyout()
, но поведение очень схожее, оба в конечном итоге вызывают raw_copy_to_user()
(который зависит от архитектуры).
А как насчет асинхронных интерфейсов ввода-вывода, таких как AIO и io_uring
? Они также копируют?
Функции aio_{read,write}
lib c, определенные POSIX, являются просто асинхронными оболочками вокруг read
и write
(т.е. они все еще используют read
и write
под капотом). Они по-прежнему копируют данные в / из пространства пользователя.
io_uring
может обеспечить операции нулевого копирования при использовании флага O_DIRECT
open
(см. Справочную страницу ):
O_DIRECT (since Linux 2.4.10)
Try to minimize cache effects of the I/O to and from this
file. In general this will degrade performance, but it is
useful in special situations, such as when applications do
their own caching. File I/O is done directly to/from user-
space buffers. The O_DIRECT flag on its own makes an effort
to transfer data synchronously, but does not give the
guarantees of the O_SYNC flag that data and necessary metadata
are transferred. To guarantee synchronous I/O, O_SYNC must be
used in addition to O_DIRECT. See NOTES below for further
discussion.
Это следует делать осторожно, поскольку это может очень сильно ухудшить производительность в случае, если приложение пользовательского пространства не выполняет соответствующее кэширование самостоятельно (при необходимости).
См. Также эту связанную подробную ответ об асинхронном вводе-выводе и этой статье LWN на io_uring
.