В этой статье исследуется LinuxОсновные технологии нулевого копированияи технология нулевого копированияПрименимые сценарии. Чтобы быстро установить концепцию нулевого копирования, мы вводим общий сценарий:
Цитата
При написании серверной программы (веб-сервера или файлового сервера) загрузка файлов является основной функцией. В это время задачей сервера является:Отправьте файл на хост-диск сервера из подключенного сокета без изменений, мы обычно делаем это с помощью следующего кода:
while((n = read(diskfd, buf, BUF_SIZE)) > 0)
write(sockfd, buf , n);
Основная операция заключается в циклическом чтении содержимого файла с диска в буфер, а затем отправке содержимого буфера вsocket
. Но из-за линуксаI/O
Операция по умолчанию для буферизацииI/O
. Основное использование здесьread
иwrite
Два системных вызова, мы не знаем, что в них делает ОС. На самом деле вышеI/O
Во время операции произошло несколько копий данных.
Когда приложение обращается к определенному фрагменту данных, операционная система сначала проверяет, был ли недавно доступ к файлу и кэшируется ли содержимое файла в буфере ядра.read
обеспечивается системным вызовомbuf
адрес, скопируйте содержимое буфера ядра вbuf
в указанный буфер пользовательского пространства. Если нет, операционная система сначала копирует данные с диска в буфер ядра.DMA
для передачи, а затем скопировать содержимое буфера ядра в пользовательский буфер.
Следующий,write
Затем системный вызов копирует содержимое пользовательского буфера в буфер ядра, относящийся к сетевому стеку, и, наконец,socket
Затем отправьте содержимое буфера ядра на сетевую карту.
Сказав так много, лучше смотреть на картинку четко:
DMA
Для связи с оборудованием центральному процессору по-прежнему необходимо обрабатывать две копии данных, при этом в пользовательском режиме и в режиме ядра происходит многократное переключение контекста, что, несомненно, увеличивает нагрузку на процессор.
Во время этого процесса мы не вносили никаких изменений в содержимое файла, поэтому копирование данных туда и обратно между пространством ядра и пространством пользователя, несомненно, является пустой тратой времени, и нулевое копирование в основном предназначено для решения этой неэффективности.
Что такое технология нулевого копирования (zero-copy)? ##
Основная задача нулевого копирования состоит в том, чтобыизбегатьЦП копирует данные из одной части хранилища в другую, в основном с помощью различных технологий нулевого копирования, чтобы ЦП не выполнял множество задач копирования данных, уменьшал количество ненужных копий или позволял другим компонентам выполнять такие простые задачи передачи данных. освободить ЦП, чтобы сосредоточиться на других задачах. Это позволяет более эффективно использовать системные ресурсы.
Продолжаем возвращаться к примеру в цитате, как можно уменьшить количество копий данных? Очевидная цель — уменьшить копирование данных туда и обратно между пространством ядра и пространством пользователя, что также вводит тип нулевого копирования:
Пусть передача данных не должна проходить через пространство пользователя
использовать mmap
Одним из способов уменьшить количество копий является вызов mmap() вместо вызова чтения:
buf = mmap(diskfd, len);
write(sockfd, buf, len);
вызов приложенияmmap()
, данные на диске проходят черезDMA
Скопированный буфер ядра, затем операционная система будет совместно использовать этот буфер ядра с приложением, поэтому нет необходимости копировать содержимое буфера ядра в пространство пользователя. Отзыв приложенияwrite()
, операционная система напрямую копирует содержимое буфера ядра вsocket
буфер, все это происходит в режиме ядра, и, наконец,socket
Затем буфер отправляет данные на сетевую карту.
Опять же, глядя на картинку очень просто:
mmap
Есть цена. когда вы используетеmmap
Когда вы это сделаете, вы можете столкнуться с некоторыми скрытыми ловушками. Например, когда ваша программаmap
Файл создается, но когда файл усекается другим процессом, будет вызван системный вызов записи для доступа к недопустимому адресу.SIGBUS
сигнал прекращен.SIGBUS
Сигнал по умолчанию уничтожит ваш процесс и создастcoredump
, если ваш сервер будет приостановлен таким образом, он понесет убытки.
Обычно мы используем следующие решения, чтобы избежать такого рода проблем:
-
Установите обработчик сигнала для сигнала SIGBUS.при встрече
SIGBUS
сигнал, обработчик сигнала просто возвращает значение,write
Системный вызов возвращает количество байтов, записанных до прерывания, иerrno
будет установлен на успех, но это плохой способ справиться с этим, потому что вы не решаете настоящую суть проблемы. -
Использовать блокировки аренды файловОбычно мы используем этот метод, используя блокировку аренды дескриптора файла, мы применяем блокировку аренды к ядру для файла, когда другие процессы хотят обрезать файл, ядро отправит нам в режиме реального времени
RT_SIGNAL_LEASE
Сигнал, сообщающий нам, что ядро снимает блокировку чтения-записи, которую вы установили для файла. Таким образом, программа получает доступ к недопустимой памяти иSIGBUS
прежде чем убитьwrite
Системные вызовы прерываются.write
возвращает количество записанных байтов и устанавливаетerrno
для успеха.
мы должны бытьmmap
Файл заблокирован до и разблокирован после манипулирования файлом:
if(fcntl(diskfd, F_SETSIG, RT_SIGNAL_LEASE) == -1) {
perror("kernel lease set signal");
return -1;
}
/* l_type can be F_RDLCK F_WRLCK 加锁*/
/* l_type can be F_UNLCK 解锁*/
if(fcntl(diskfd, F_SETLEASE, l_type)){
perror("kernel lease set type");
return -1;
}
Используйте SendFile
Начиная с ядра 2.1, Linux представилsendfile
упростить вещи:
#include<sys/sendfile.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
системный вызовsendfile()
в дескрипторе, представляющем входной файлin_fd
и дескриптор, представляющий выходной файлout_fd
Передача содержимого файла (байты) между ними. Дескрипторout_fd
должен указывать на сокет, иin_fd
Указанный файл должен быть доступенmmap
из. Эти ограничения ограничиваютsendfile
использовать, чтобы сделатьsendfile
Данные могут передаваться только из файла в сокет, а не наоборот.
использоватьsendfile
Не только уменьшает количество копий данных, но и уменьшает переключение контекста, передача данных всегда происходит только вkernel space
.
когда мы звонимsendfile
, что произойдет, если другой процесс обрежет файл? Предполагая, что мы не устанавливаем никаких обработчиков сигналов,sendfile
Вызов просто возвращает количество байтов, которые он передал до того, как был прерван.errno
будет настроен на успех. Если мы заблокируем файл перед вызовом sendfile,sendfile
Поведение остается таким же, как и раньше, мы также получаем сигнал RT_SIGNAL_LEASE.
На данный момент мы уменьшили количество копий данных, но осталась одна копия, копия кэша страницы в кэш сокета. Так можно ли опустить и эту копию?
С помощью оборудования мы можем это сделать. Перед тем, как мы скопировали данные из кеша страниц в кеш сокета, по сути, нам просто нужно передать дескриптор буфера вsocket
буфер, а затем передать длину данных, поэтомуDMA
Контроллер может напрямую упаковывать данные в кэш страниц и отправлять их в сеть.
в заключении,sendfile
использование системного вызоваDMA
Движок копирует содержимое файла в буфер ядра, а затем добавляет дескриптор буфера с информацией о позиции и длине файла в буфер сокета.Этот шаг не копирует данные из ядра в буфер сокета.DMA
Механизм скопирует данные из буфера ядра в механизм протокола, избегая последней копии.
Однако эта функция копирования коллекции требует поддержки оборудования и драйверов.
использовать сращивание
sendfile подходит только для копирования данных из файла в сокет, что ограничивает область его использования. Linux в2.6.17
введение версииsplice
Системный вызов для перемещения данных между двумя файловыми дескрипторами:
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <fcntl.h>
ssize_t splice(int fd_in, loff_t *off_in, int fd_out, loff_t *off_out, size_t len, unsigned int flags);
Вызов splice перемещает данные между двумя файловыми дескрипторами без копирования данных туда и обратно между пространством ядра и пространством пользователя. он изfd_in
копироватьlen
длина данных доfd_out
, но одна сторона должна быть конвейерным устройством, которое также в настоящее времяsplice
некоторые ограничения.flags
Параметры имеют следующие значения:
-
SPLICE_F_MOVE: Попробуйте переместить данные, а не копировать их. Это всего лишь небольшая подсказка ядру: если ядро не может
pipe
мобильные данные илиpipe
Кэш не является полной страницей и все еще требует копирования данных. Были некоторые проблемы с исходной реализацией Linux, поэтому из2.6.21
Сначала эта опция не работает, в более поздних версиях Linux она должна быть реализована. -
SPLICE_F_NONBLOCK:
splice
Операция не будет заблокирована. Однако, если файловый дескриптор не установлен в I / O, который не препятствует, то SPLICE вызовов все еще может быть заблокирован. -
SPLICE_F_MORE: Назад
splice
В вызове будет больше данных.
Вызов splice использует механизм конвейерного буфера, предложенный Linux, поэтому по крайней мере один дескриптор должен быть каналом.
Вышеупомянутые технологии нулевого копирования реализованы за счет сокращения копирования данных в пространстве пользователя и пространстве ядра, но иногда данные необходимо копировать между пространством пользователя и пространством ядра. В настоящее время мы можем работать только над синхронизацией копирования данных в пространстве пользователя и пространстве ядра. Linux обычно использует **копирование при записи** для снижения нагрузки на систему, этот метод часто называютCOW
.
Из-за недостатка места в этой статье подробно не рассматривается копирование при записи. Общее описание таково: если несколько программ обращаются к одному и тому же фрагменту данных одновременно, то каждая программа имеет указатель на этот фрагмент данных.С точки зрения каждой программы, она владеет этим фрагментом данных независимо, только когда программа должна: Когда содержимое данных будет изменено, содержимое данных будет скопировано в пространство приложения самой программы. В это время данные станут личными данными программы. Если программе не нужно изменять данные, ей никогда не потребуется копировать данные в собственное пространство приложения. Это уменьшает копирование данных. Содержание, скопированное во время написания, может быть написано в другой статье. . .
Кроме того, есть методы нулевой копии, такие как обычные Linux I / O плюсO_DIRECT
Отметить можно прямоI/O
, который позволяет избежать автоматического кэширования, и незрелыйfbufs
Технология, в этой статье не рассматриваются все технологии с нулевым копированием, а только представлены некоторые распространенные. Если вам интересно, вы можете изучить их самостоятельно. Как правило, зрелые проекты на стороне сервера также преобразуют часть, связанную с вводом-выводом, ядра сами по себе, чтобы улучшить скорость передачи данных.