LINUX.ORG.RU

mmap + madvise верно использую?

 , ,


0

1

Приветствую.

В продолжении темы

Задача отдать одновременно записываемый видео архив в сеть с флешки без файловой системы в один поток после успешного боевого тестирования идеи rtsp публикацией перетекла в задачу многопотока (предварительно до 50 штук). Как советовали в той теме решил попробовать mmap всего сырого носителя иба ручное управление буфером как то не улыбает.

Наваял нечто похожее

if ((fd = open(device, O_RDONLY)) < 0)
   fprintf(...);
else if ((md = mmap(NULL, partition_size, PROT_READ, MAP_SHARED, fd, (first_lba - 1) * sector_size + 1)) == MAP_FAILED)
   close(fd);
else if (madvise(md, partition_size, MADV_SEQUENTIAL) < 0)
   fprintf(...);

Вопросы

  1. корректно ли выполнять madvise и munmap без offset с учетом того что он не нулевой только на величину partition_size?
  2. при обращении к (uint8_t*)md + позиция_байта offset надо учитывать?
  3. на всякий случай - потокобезопасен п.2 так же как и системный вызов read/pread с учетом того что выполняется запись через fwrite/fflush со своим дескриптором?
★★★

Последнее исправление: wolverin (всего исправлений: 2)
  1. У тебя mmap с ненулевым offset. Это нормально. Это значит надо отобразить в память не весь файл, а начиная с указанного смещения в файле. Сколько байт надо отобразить – задаёт partition_size.

  2. Байт (uint8_t*)md + n в памяти это байт offset + n в файле.

  3. fwrite пишет в stdio буфер в юзерспейсе. fflush сбрасывает stdio буфер в блочный кэш ядра. Отмапленный таким образом регион это блочный кэш ядра. Если один поток выполняет fflush, а другой поток читает отмапленый регион, то это data race.

iliyap ★★★★★
()
Ответ на: комментарий от iliyap

спасибо, т.е. madvise/munmap корректно выполнять только на размер partition_size без offset

другой поток читает отмапленый регион

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

wolverin ★★★
() автор топика
Ответ на: комментарий от wolverin

sysconf(_SC_PAGE_SIZE) на арм тоже оказывается 4к страница

) но все равно как то туплю - если первый сектор с которого можно писать это 2048, то какой offset для mmap должен быть чтобы начать с первого байта этого сектора…

wolverin ★★★
() автор топика
Последнее исправление: wolverin (всего исправлений: 1)

ладно, вроде и так все работает, будем считать что замена read на mmap мне существенно снизило потребление памяти, а может и отклик уменьшило при старте - точно могу сказать только что код уменьшило почти в 2 раза ))

wolverin ★★★
() автор топика
Последнее исправление: wolverin (всего исправлений: 3)
Ответ на: комментарий от wolverin

Если 32-битная то не то что нельзя больше 2гб mmap, нельзя вообще больше 2гб чего угодно одновременно выделять.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 1)
Ответ на: комментарий от wolverin

Что значит не выделяет? Твой бракованный может и не выделяет, раз при обращении к якобы выделенным адресам сегфолт происходит. А в норме - выделяет указанное количество адресов и мапит туда файл.

firkax ★★★★★
()
Ответ на: комментарий от firkax

ну как я понял теорию - ошибка при обращении и приводит к чтению с диска в память, так визуально не вижу большого роста занятой памяти сразу после ммап.

wolverin ★★★
() автор топика
Последнее исправление: wolverin (всего исправлений: 1)
Ответ на: комментарий от wolverin

mmap (без флага MAP_POPULATE) не выделяет физические страницы, но он выделяет место в адресном пространстве процесса. Если места в адресном пространстве процесса не достаточно, он должен вернуть MAP_FAILED.

Можно форкнуть несколько процессов, и в каждом смапить разные куски файла. Но это дурацкая идейка кмк.

iliyap ★★★★★
()
Ответ на: комментарий от wolverin

Ты не сможешь отмапить больше одного 2 GiB куска в одно 32-битное адресное пространство. Вот пример. Программка мапит куски размером 1 GiB, пока mmap не вернёт MAP_FAILED:

#include <fcntl.h>
#include <signal.h>
#include <stddef.h>
#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>

int main(int argc, char** argv)
{
    int fd;
    void* base;
    sigset_t set;
    int signo;
    size_t size;
    off_t offset;

    if (argc != 2) return 1;

    size = 0x40000000; /* 1 GiB */
    offset = 0;
    for (;;) {
        fd = open(argv[1], O_RDONLY | O_CLOEXEC);
        base = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, offset);
        close(fd);
        if (base == MAP_FAILED)
            break;
        printf("base=%p\n", base);
        offset += size;
    }

    sigemptyset(&set);
    sigaddset(&set, SIGINT);
    sigwait(&set, &signo);
    return 0;
}

Компилируем в 32-битный бинарник (чтобы при запуске программки получился 32-битный процесс с 32-битным адресным пространством), и запускаем:

$ gcc -Wall -Wextra -pedantic -m32 -o map map.c
$ ./map /dev/nvme0n1p4
base=0xb7d0a000
base=0x77d0a000
base=0x37d0a000

Программа смогла отмапить только 3 GiB. Четвёртый одногигабайтный кусок отмапить уже не удалось. 32-битное адресное пространство имеет размер 4 GiB, а часть его уже занята (кодом, данными и стеком программы).

iliyap ★★★★★
()
Ответ на: комментарий от wolverin

Там в конце программы sigwait, в нём и висит. Чтобы можно было в procfs посмотреть мапинги процесса. Не веришь, что висит в sigwait? Закомментируй его, перекомпилируй и перезапусти.

Я запускал на 64-битном x86_64 (64-битное ядро, 32-битный процесс). В этом случае процессу доступны все 4 GiB адресного пространства. Код и данные программы занимают младшие адреса, стек – старшие. Между ними дырка для mmap-а. У меня та дырка заканчивается видимо на 0xb7d0a000 + 0x40000000 = 0xf7d0a000.

У тебя 32-разрядное ядро и 32-разрядный процесс. Процессу доступны 3 GiB из адресного пространста, верхний 1 GiB зарезервирован для ядра. (Когда процесс входит в ядро сисколлом, ядру нужно иметь доступ одновременно к собственным коду и данным, и к данным процесса, вызвавшего сисколл. Поэтому на 32-битных системах верхний 1 GiB адресного пространства недоступен процессу.)

Поэтому у тебя доступное адресное пространство заканчивается на 0xc0000000, а mmap-дырка заканчивается на 0x76e2f000 + 0x40000000 = 0xb6e2f000.

iliyap ★★★★★
()
Последнее исправление: iliyap (всего исправлений: 1)
Ответ на: комментарий от iliyap

прикола ради

#include <fcntl.h>
#include <signal.h>
#include <stddef.h>
#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdint.h>

int main(int argc, char** argv)
{
    int fd;
    void* base;
    sigset_t set;
    int signo;
    uint64_t size;
    off_t offset;

    if (argc != 2) return 1;

    size = (uint64_t)1024 * 1024 * 1024 * 10;
    offset = 0;
    for (;;) {
        fd = open(argv[1], O_RDONLY | O_CLOEXEC);
        base = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, offset);
        close(fd);
        if (base == MAP_FAILED)
            break;
        printf("base=%p\n", base);
        offset += size;
    }

    return 0;
}

base=0x36eb8000
wolverin ★★★
() автор топика
Ответ на: комментарий от wolverin

Второй аргумент mmap имеет тип size_t. У 32-битных процессов size_t 32-битный. У тебя здесь произошёл молчаливый каст из uint64_t в size_t, который отрезал старшие биты от 10 GiB = 10*1024*1024*1024 = 0x2'8000'0000, получилось 0x8000'0000, т.е. 2 GiB. Один 2 GiB кусок в 32-битный процесс смапить можно.

iliyap ★★★★★
()

нет ну хорошо, тогда остается вариант сравнить pread из разных потоков и mmap из каждого кусками, размер которых будет подобран под максимальное количество коннектов

wolverin ★★★
() автор топика
Последнее исправление: wolverin (всего исправлений: 1)
Ответ на: комментарий от iliyap

… У тебя 32-разрядное ядро и 32-разрядный процесс. Процессу доступны 3 GiB из адресного пространста, верхний 1 GiB зарезервирован для ядра

зависит от, на примере linux-3.10.33 доступны разделеняи памяти как 3G/1G user/kernel split, 2G/2G user/kernel split и 1G/3G user/kernel split

anonymous
()