Анализ технологии Zero Copy в Linux

Операционная система Linux

В этой статье исследуется 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 вместо read заведомо уменьшает одну копию, а при большом количестве копируемых данных несомненно повышает эффективность. но использоватьmmapЕсть цена. когда вы используетеmmapКогда вы это сделаете, вы можете столкнуться с некоторыми скрытыми ловушками. Например, когда ваша программаmapФайл создается, но когда файл усекается другим процессом, будет вызван системный вызов записи для доступа к недопустимому адресу.SIGBUSсигнал прекращен.SIGBUSСигнал по умолчанию уничтожит ваш процесс и создастcoredump, если ваш сервер будет приостановлен таким образом, он понесет убытки.

Обычно мы используем следующие решения, чтобы избежать такого рода проблем:

  1. Установите обработчик сигнала для сигнала SIGBUS.при встречеSIGBUSсигнал, обработчик сигнала просто возвращает значение,writeСистемный вызов возвращает количество байтов, записанных до прерывания, иerrnoбудет установлен на успех, но это плохой способ справиться с этим, потому что вы не решаете настоящую суть проблемы.
  2. Использовать блокировки аренды файловОбычно мы используем этот метод, используя блокировку аренды дескриптора файла, мы применяем блокировку аренды к ядру для файла, когда другие процессы хотят обрезать файл, ядро ​​отправит нам в режиме реального времени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, что произойдет, если другой процесс обрежет файл? Предполагая, что мы не устанавливаем никаких обработчиков сигналов,sendfileВызов просто возвращает количество байтов, которые он передал до того, как был прерван.errnoбудет настроен на успех. Если мы заблокируем файл перед вызовом sendfile,sendfileПоведение остается таким же, как и раньше, мы также получаем сигнал RT_SIGNAL_LEASE.

На данный момент мы уменьшили количество копий данных, но осталась одна копия, копия кэша страницы в кэш сокета. Так можно ли опустить и эту копию?

С помощью оборудования мы можем это сделать. Перед тем, как мы скопировали данные из кеша страниц в кеш сокета, по сути, нам просто нужно передать дескриптор буфера вsocketбуфер, а затем передать длину данных, поэтомуDMAКонтроллер может напрямую упаковывать данные в кэш страниц и отправлять их в сеть.

в заключении,sendfileиспользование системного вызоваDMAДвижок копирует содержимое файла в буфер ядра, а затем добавляет дескриптор буфера с информацией о позиции и длине файла в буфер сокета.Этот шаг не копирует данные из ядра в буфер сокета.DMAМеханизм скопирует данные из буфера ядра в механизм протокола, избегая последней копии.

带DMA的sendfile

Однако эта функция копирования коллекции требует поддержки оборудования и драйверов.

использовать сращивание

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Технология, в этой статье не рассматриваются все технологии с нулевым копированием, а только представлены некоторые распространенные. Если вам интересно, вы можете изучить их самостоятельно. Как правило, зрелые проекты на стороне сервера также преобразуют часть, связанную с вводом-выводом, ядра сами по себе, чтобы улучшить скорость передачи данных.