0x01 Введение: традиционный режим копирования
Практическим сценарием является статический файловый сервер, где клиент запрашивает статический ресурс, а сервер возвращает ему содержимое. Традиционный способ обработки такой (обратите внимание, для краткости кода часть кода опущена)
for (;;) {
if (lseek(fd, 0, SEEK_SET) < 0) err_quit("error seek file");
connect_fd = accept(listen_fd, &serv_addr, &client_addr);
if (connect_fd < 0) err_quit("accept failed");
int n = 0;
char buf[BUFFER_SIZE];
while ((n = read(fd, buf, BUFFER_SIZE)) > 0) {
write(connect_fd, buf, n);
}
close(connect_fd);
}
Для многих приложений такое циклическое чтение и запись приемлемо, но если нам нужно часто передавать большие файлы через сокет, этот метод очень неэффективен: этот процесс включает4 копии данных,Как показано ниже
Подробная временная диаграмма выглядит следующим образом:
- Выполнить системный вызов чтения
- Контекст операционной системы переключается в режим ядра, а DMA считывает содержимое файла с диска и сохраняет его в буферном кеше ядра (1-й переключатель контекста, 1-я копия данных)
- Ядро операционной системы копирует данные из ядра в буфер пользовательского пространства, контекст переключается в пользовательский режим, и функция read() завершает работу (второе переключение контекста, вторая копия данных).
- Выполните некоторый возможный бизнес-код, затем выполните системный вызов записи
- Контекст операционной системы переключается в режим ядра, а затем данные копируются из пространства пользователя в буфер отправки сокета в пространстве ядра (3-й переключатель контекста, 3-я копия данных)
- Функция записи возвращается, операционная система переходит в пользовательский режим, и в то же время происходит четвертое копирование, DMA копирует данные из буфера ядра в механизм обработки сетевого протокола сетевой карты (четвертое переключение контекста, четвертое копирование данных )
Видно, что задействованы две копии между пространством пользователя и пространством ядра, одна используется для копирования содержимого файла из кеша буфера ядра в пространство пользователя, а другая используется для копирования буфера пространства пользователя в пространство ядра. так как это может быть передано только через сокеты.Если приложению вообще не нужно ничего делать с содержимым файла перед тем, как инициировать передачу, то такое копирование туда-сюда совершенно бесполезно.
0x02 лучше
Начиная с версии ядра 2.1, в Linux для упрощения операции введен sendfile.Метод sendfile успешно уменьшает количество копий данных с четырех до трех (только одна из которых использует ЦП), но этот метод еще не достиг нулевого копирования. требование. Основной код выглядит следующим образом
for (;;) {
connect_fd = accept(listen_fd, &serv_addr, &client_addr);
if (connect_fd < 0) err_quit("accept failed");
struct stat stat_buf;
fstat(fd, &stat_buf);
off_t offset = 0;
int cnt = 0;
if ((cnt = sendfile(connect_fd, fd, &offset, stat_buf.st_size)) < 0) {
err_quit("send file failed");
}
close(connect_fd);
}
Конкретная блок-схема выглядит следующим образом
- Выполнить системный вызов sendfile
- DMA считывает содержимое файла с диска и сохраняет его в буферном кеше ядра.
- Скопируйте данные из буфера ядра в буфер отправки сокета.
- Механизм DMA передает данные из буфера отправки сокета в механизм протокола.
0x03 Настоящая нулевая копия
Когда сетевая карта поддерживаетscatter-n-gather
режиме, вы также можете удалить среднюю копию в 0x02
Конкретный процесс показан на следующем рисунке:
Конкретная блок-схема выглядит следующим образом
На данный момент копия данных не имеет ничего общего с ЦП, она полностью управляется ядром, а ядру нужны только две копии.
0x04 Тестовый код
Репозиторий кода выглядит следующим образом:GitHub.com/Артур-Станция…Моделирование представляет собой сервер статических ресурсов.Когда клиент подключается, он возвращает содержимое файла статических ресурсов index.html. Способ проверки заключается в использованииtelnet localhost 34567
Имитация клиентского подключения