LINUX.ORG.RU

mmap из ядра в приложение.


0

2

ЛЮди, у кого есть реально работающий пример, когда кусок памяти из ядра, можно отмапить в память юзер-спейс.
mmap_t mmap __attribute__ ((aligned(4096)));

static int
fops_mmap(struct file *file, struct vm_area_struct *vma)
{

FileData_t *fd;
//static mmap_t *mmapTest = kmalloc(sizeof(mmap_t)+ 4096*2, GFP_USER);
unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
unsigned long size = vma->vm_end - vma->vm_start;

//mmapTest

fd = file->private_data;

printk (KERN_INFO «fops_mmap offset = %lX \n», offset);
printk (KERN_INFO «fops_mmap size = %lX %lu \n», size, size);
printk (KERN_INFO «fops_mmap vm_end = %lX \n», vma->vm_end);
printk (KERN_INFO «fops_mmap vm_start = %lX \n», vma->vm_start);
printk (KERN_INFO «mmap = %p \n»,(void*)&mmap);
//printk (KERN_INFO «mmapTest = %p \n»,(void*)&mmapTest);


if (offset & ~PAGE_MASK)
{
printk(«offset not aligned: %ld\n», offset);
return -ENXIO;
}

if (size > (sizeof(mmap_t)+4096LL))
{
printk(
«size too big. vma->vm_end-vma->vm_start=%lu sizeof(mmap_t)=%lu diff=%llu \n»,
vma->vm_end - vma->vm_start, sizeof(mmap_t), sizeof(mmap_t) +4096LL - size);
return (-ENXIO);
}

if ((vma->vm_flags & VM_WRITE) && !(vma->vm_flags & VM_SHARED))
{
printk(«writeable mappings must be shared, rejecting\n»);
return (-EINVAL);
}

/* we do not want to have this area swapped out, lock it */
vma->vm_flags |= VM_LOCKED;

//memset(&mmapTest,5,sizeof(mmapTest));
memset(&mmap,5,sizeof(mmap_t));

{
void *vmalloc_area_ptr = &mmap;
unsigned start = vma->vm_start;
int ret;
while (size > 0) {
unsigned pfn = vmalloc_to_pfn(vmalloc_area_ptr);
if ((ret = remap_pfn_range(vma, start, pfn, PAGE_SIZE,
PAGE_SHARED)) < 0) {
return ret;
}
start += PAGE_SIZE;
vmalloc_area_ptr += PAGE_SIZE;
size -= PAGE_SIZE;
}
}
/*if (remap_pfn_range(vma,vma->vm_start,virt_to_phys((void*)&mmap)>>PAGE_SHIFT, size, PAGE_SHARED))
{
printk(«remap page range failed\n»);
return -ENXIO;
}*/
printk(«remap page range OK!!! \n»);
//return -ENXIO;
return (0);

}


В юзер-спейс, два один тред инкрементирует переменную в в mmap, и выводит ее на экран. Другой тред просто выводит ее на экран. Все работает. Но из ядра я обмениваться данными с юзер-спейс не могу. Хоть и есть memset, но читаются одни нули. Что мемсет обнуляет, я понять не могу.

Ты неправильно мапишь. Как и написано в учебнике (LDD3, ага), remap_pfn_range предназначена для настоящих мужчин^W^Wмапинга памяти устройств в драйверах. А тебе нужен vm_insert или vm_insert_pfn. Краткий работающий пример см. в linux.drivers/char/mspеc.c, несколько более длинный - в linux/drivers/firewire/*

tailgunner ★★★★★
()

Держи пример

Из файла drivers/char/mem.c:

static int mmap_mem(struct file *file, struct vm_area_struct *vma)
{
        size_t size = vma->vm_end - vma->vm_start;

        if (!valid_mmap_phys_addr_range(vma->vm_pgoff, size))
                return -EINVAL;

        if (!private_mapping_ok(vma))
                return -ENOSYS;

        if (!range_is_allowed(vma->vm_pgoff, size))
                return -EPERM;

        if (!phys_mem_access_prot_allowed(file, vma->vm_pgoff, size,
                                                &vma->vm_page_prot))
                return -EINVAL;

        vma->vm_page_prot = phys_mem_access_prot(file, vma->vm_pgoff,
                                                 size,
                                                 vma->vm_page_prot);

        vma->vm_ops = &mmap_mem_ops;

        /* Remap-pfn-range will mark the range VM_IO and VM_RESERVED */
        if (remap_pfn_range(vma,
                            vma->vm_start,
                            vma->vm_pgoff,
                            size,
                            vma->vm_page_prot)) {
                return -EAGAIN;
        }
        return 0;
}
ttnl ★★★★★
()
Ответ на: комментарий от tailgunner

Понятно. Спасибо. linux.drivers/char/mspеc.c не понятно как потом к этим страницам достучаться из ядра.

С памятью для DMA, remap_pfn_range работало нормально. Но мне нужен кусок памяти, под промежуточный буфер, большого размера, который система мне не выделит одним непрерывным куском.

Artem-Dnepr
() автор топика
Ответ на: комментарий от Artem-Dnepr

> С памятью для DMA, remap_pfn_range работало нормально. Но мне нужен кусок памяти, под промежуточный буфер, большого размера, который система мне не выделит одним непрерывным куском.

По-моему, ты что-то делаешь не так.

tailgunner ★★★★★
()
Ответ на: Держи пример от ttnl

> Держи пример Из файла drivers/char/mem.c:

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

Artem-Dnepr
() автор топика
Ответ на: комментарий от tailgunner

> По-моему, ты что-то делаешь не так.

Мне тоже так начинает казаться.

В общем, в системе (х86, 64 бита) есть PCI или PCIe карточки. Они через DMA в режиме бас-мастеринга складывают все в ОЗУ. Дальше мне нужно эти данные «по-быстрому» обработать, перетасовать, и сложить в большой буфер для дальнейшей обработке в юзер-спейс.

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

Artem-Dnepr
() автор топика
Ответ на: комментарий от Artem-Dnepr

А... тогда, может, и правильно. Я бы сделал так: замапил сколько надо страниц для DMA, получил данные, и потом эти же страницы замапил для юзера (предварительно сняв DMA-мапинг, конечно); всё - через vma_insert_page. Есть только одно «но» - непонятно, сколько именно данных должна мапить пользовательская программа: тут надо либо ставить в драйвере обработчик страничного сбоя, чтобы программа могла его поймать, либо у данных с устройства должен быть какой-то заголовок. Ну, или программа должна еще откуда-то знать, сколько данных мапить.

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

Впрочем, есть и другой способ: замапить для DMA непосредственно страницы пользовательского процесса, через get_user_pages.

tailgunner ★★★★★
()
Ответ на: комментарий от Artem-Dnepr

>Мне не нужно физическую память из ядра отмапить мне нужно виртуальную.

В чем проблема преобразовать адрес?

Которая идет постранично

В чем проблема применить несколько раз remap_pfn_range?

и может быть большого размера

Все относительно

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

Памяти, нужно примерно несколько десятков мегабайт. Linux одним куском pci_alloc_coherent дает только мегабайт. Бить по кускам не хочется. Возиться с обработчиками page fault, еще больше.

Есть какой-то простой способ сделать кусок памяти в несколько десятков мегабайт, общим у кернела и приложения?

Artem-Dnepr
() автор топика
Ответ на: комментарий от ttnl

> /dev/mem (drivers/char/mem.c)

для физической памяти. Мне же нужна виртуальная. У меня в ядре есть указатель на виртуальную память (которой физически вообще может не быть). И я хочу с ней работать в юзер-спейс. Я могу получить первый блок памяти физической памяти, и замапить его в юзер-спейс, но что будет если он в процессе работы станет другим итд.

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

Artem-Dnepr
() автор топика
Ответ на: комментарий от Artem-Dnepr

>для физической памяти. Мне же нужна виртуальная. У меня в ядре есть указатель на виртуальную память (которой физически вообще может не быть). И я хочу с ней работать в юзер-спейс.

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

Монструозного механизма, который бы автоматически херачил таблицу страниц при каждом их перемещении, нет.

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

> Монструозного механизма, который бы автоматически херачил таблицу страниц при каждом их перемещении, нет.

Ну вот я не могу найти, как не физическую память отмпаить в юзер-спейс.

Artem-Dnepr
() автор топика
Ответ на: комментарий от ttnl

Сделал.

mmap.rx0.testVal = 0x6666;
  mmap.rx1.testVal = 0x9999;
  {
    void *vmalloc_area_ptr = &mmap;
    unsigned start = vma->vm_start;
    int ret;
    while (size > 0) {
                 unsigned pfn = vmalloc_to_pfn(vmalloc_area_ptr);
                 SetPageReserved(vmalloc_to_page( (void*)vmalloc_area_ptr) );
                 printk("pfn=%x vmalloc_area_ptr=%p\n",pfn,vmalloc_area_ptr);
                 if ((ret = remap_pfn_range(vma, start, pfn, PAGE_SIZE,
                                            PAGE_SHARED)) < 0) {
                         return ret;
                 }
                 start += PAGE_SIZE;
                 vmalloc_area_ptr += PAGE_SIZE;
                 size -= PAGE_SIZE;
         }
  }
В юзер-спейсе, тестVal равны нулям.

Artem-Dnepr
() автор топика
Ответ на: комментарий от Artem-Dnepr

>В юзер-спейсе, тестVal равны нулям.

Если в ванильном ядре работает, а у тебя нет, то это значит, что неправильно у тебя. Ищи ошибку.

Кстати, mmap_t mmap __attribute__ ((aligned(4096))), это не vmalloc

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

Заменил.

mmap = vmalloc(sizeof(mmap_t)+PAGE_SIZE*2);
mmap = (void *)(((unsigned long)mmap + PAGE_SIZE -1) & PAGE_MASK);
Все равно в юзер-спейс, все ок, а чтобы ни писал из ядра, в юзер-спейсе не читается.

Artem-Dnepr
() автор топика
Ответ на: комментарий от Artem-Dnepr

Я совсем поехал.

Прога запускает две нити. В одной открывается /dev/fff0 в другой /dev/fff1.

Обе нити делают
mm = (mmap_t*)::mmap(NULL,sizeof(mmap_t),PROT_READ | PROT_WRITE ,MAP_SHARED,m_fd,0);

В обоих нитях получаются разные указатели mm. Что правильно.

Но в ядре, для каждого из вызовов mmap, вызывается remap_pfn_range с совершенно одинаковыми pfn. Тоесть данные должны писаться в одни и теже страницы, пусть по разным указателями. А в итоге получается, что у двух нитей, и ядра три совершенно разных буфера. Как такое вообще может быть? vmalloc вызывается только один раз, при загрузке модуля.

Artem-Dnepr
() автор топика
Ответ на: комментарий от ttnl

>> Даже один раз не работает.

/dev/mem (drivers/char/mem.c)

LDD3: «remap_pfn_range won't allow you to remap conventional addresses, which include the ones you obtain by calling get_free_page. Instead, it maps in the zero page. Everything appears to work, with the exception that the process sees private, zero-filled pages rather than the remapped RAM that it was hoping for».

Насколько я понимаю, кошерный православный способ - именно vm_insert_page.

tailgunner ★★★★★
()
Ответ на: комментарий от Artem-Dnepr

> Памяти, нужно примерно несколько десятков мегабайт. Linux одним куском pci_alloc_coherent дает только мегабайт.

Размещай страницами.

Бить по кускам не хочется.

Не уверен, что у тебя есть другой выход :)

Возиться с обработчиками page fault, еще больше.

Итак, твои варианты: 1) выделить в драйвере сколько нужно страниц, замапить их, провести DMA, после чего замапить их через vm_insert_page - в этом случае обработчик сбоя страницы состоит из одной строки; 2) залочить память пользовательского процесса, переданную в read, провести DMA туда, и на этом закончить. Оба варианта требуют наличия scatter-gather в устройстве, первый вариант проще и кошернее, время на его реализацию - одна ночь %)

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

именно vm_insert_page.

 void *vmalloc_area_ptr = mmap;
    unsigned start = vma->vm_start;
    while (size > 0) {
                 if (vm_insert_page(vma, start,vmalloc_to_page( (void*)vmalloc_area_ptr))) {
                             printk("vm_insert_page fault \n");
                                          return -EAGAIN;
                                  }
                 start += PAGE_SIZE;
                 vmalloc_area_ptr += PAGE_SIZE;
                 size -= PAGE_SIZE;
         }
  }

уходит в printk(«vm_insert_page fault \n»);

Artem-Dnepr
() автор топика
Ответ на: комментарий от tailgunner

Кхм... и ты хочешь, чтобы я по этому кусочку сказал, где у тебя ошибка?

Попробую все что относится к делу дать.

это в init_module
 mmap = vmalloc(sizeof(mmap_t)+PAGE_SIZE*2);
  mmap = (void *)(((unsigned long)mmap + PAGE_SIZE -1) & PAGE_MASK);

static int
fops_mmap(struct file *file, struct vm_area_struct *vma)
{

  unsigned long size = vma->vm_end - vma->vm_start;

  vma->vm_flags |= VM_IO;
  vma->vm_flags |= VM_RESERVED;

  memset(mmap,5,sizeof(mmap_t));
  mmap->rx0.testVal = 0x6666;
  mmap->rx1.testVal = 0x9999;
  {
    void *vmalloc_area_ptr = mmap;
    unsigned start = vma->vm_start;
    while (size > 0) {
        if (vm_insert_page(vma, start,vmalloc_to_page( (void*)vmalloc_area_ptr))) {
           printk("vm_insert_page fault \n");
           return -EAGAIN;
        }
      start += PAGE_SIZE;
      vmalloc_area_ptr += PAGE_SIZE;
      size -= PAGE_SIZE;
     }
  }
  printk("remap page range OK!!! \n");
  return (0);
}

Драл из linux-source-2.6.38/drivers/media/video/usbvision/usbvision-video.c usbvision_v4l2_mmap

Artem-Dnepr
() автор топика
Ответ на: комментарий от Artem-Dnepr

Мне уже не кажется потеря производительности такой существенной.

разница в скорости минимум на порядок будет, в свое время проверяли

я подобное решал через vmalloc + проверку абы не в свопе + scatter/gather, пример смотрел в drivers/media/common/saa*

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

Тут дело в том, что scatter/gather придется делать тоже мне, на pcie, и хочется по-быстрее (по времени затраченному на все это). DMA работает на pci_alloc_consistent. Теперь вот захотелось данные по-быстрому скармливать userspace. Если не получится, то плюну на все и через copy_to_user сделаю. Но мне почему-то кажется что оно не должно быть настолько сложно.

Artem-Dnepr
() автор топика
Ответ на: комментарий от Artem-Dnepr

Попробую все что относится к делу дать.

Прежде всего к делу относится параметр length вызова mmap

vma->vm_flags |= VM_IO;

vma->vm_flags |= VM_RESERVED;

Убери это.

Мне уже не кажется потеря производительности такой существенной.

:)

tailgunner ★★★★★
()
Ответ на: комментарий от Artem-Dnepr

> Но мне почему-то кажется что оно не должно быть настолько сложно.

Оно совсем не сложно. Правда, я передавал в vm_insert_page указатель на struct page, полученный через virt_to_page(get_free_page(...))

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

Прежде всего к делу относится параметр length вызова mmap

А что с ним? там просто sizeof.

Убери это.

убрал. Таже фигня.

Artem-Dnepr
() автор топика
Ответ на: комментарий от tailgunner

Оно совсем не сложно. Правда, я передавал в vm_insert_page указатель на struct page, полученный через virt_to_page(get_free_page(...))

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

Artem-Dnepr
() автор топика
Ответ на: комментарий от Artem-Dnepr

>> Прежде всего к делу относится параметр length вызова mmap

А что с ним? там просто sizeof.

Чему он равен и на какой итерации у тебя слом?

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

Ну естественно, я сохранял указатель в списке. Но получен он был от get_free_page, а не от vmalloc - может, в этом проблема.

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

unsigned start = vma->vm_start; скорее всего unsigned long?

Что-то мне подсказываешь что тебе надо еще кеш сбросить после модификации.

sn1ln
()
Ответ на: комментарий от tailgunner

Чему он равен и на какой итерации у тебя слом?

мегабайт 13 наверное. Что такое слом?

Но получен он был от get_free_page, а не от vmalloc

Но vm_insert_page работает просто со страницами.

Artem-Dnepr
() автор топика
Ответ на: комментарий от Artem-Dnepr

>> Чему он равен и на какой итерации у тебя слом?

мегабайт 13 наверное.

То есть ты пытаешься замапить 13мег?

Что такое слом?

O_o Это когда программа ломается. В твоем случае - вызов vm_insert_page выдает ошибку.

Но получен он был от get_free_page, а не от vmalloc

Но vm_insert_page работает просто со страницами.

Внезапно, я знаю. Начни с малого - замапь одну страницу, выделенную get_free_pages.

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

Там ещё есть

An interesting limitation of remap_pfn_range is that it gives access only to reserved pages and physical addresses above the top of physical memory. In Linux, a page of physical addresses is marked as «reserved» in the memory map to indicate that it is not available for memory management.

Насколько я понимаю, кошерный православный способ - именно vm_insert_page.

Таблица страниц, она и в Африке таблица страниц. Так что православность того или иного способа — вопрос спорный. Могу на спор написать работающий код через remap_pfn_range ;) Впрочем, пример уже есть в drivers/video/vfb.c

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

> Таблица страниц, она и в Африке таблица страниц

Кхм. Есть нормальный, typesafe, рекомендованный к использованию API, решающий поставленную задачу. В этой ситуации ты можешь привести хоть один довод за манипуляцию номерами фреймов?

Могу на спор написать работающий код через remap_pfn_range ;)

Зачем? Чтобы доказать, что ты круче Рубини и Хартмана? Я лично предпочитаю не идти против учебников без веских причин.

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

То есть ты пытаешься замапить 13мег?

Именно. Одним куском такое не выделить без гемороя. И это минимум. Реально хотелосьбы раз в 10 больше. Грубо говоря, в ядре - реалтайм. В юзер-спейс очень мягкий реалтайм, чтобы состыковать их нужен буфер который проглотит раскручивание HDD после слипа, тормоза флешки итд.

O_o Это когда программа ломается.

Можно было бы написать на каком языке написано было. При первом обращении падает.

Artem-Dnepr
() автор топика
Ответ на: комментарий от tailgunner

Я лично предпочитаю не идти против учебников без веских причин.

Именно. Хочется обойтись без походов по граблям.

Artem-Dnepr
() автор топика
Ответ на: комментарий от tailgunner

>Кхм. Есть нормальный, typesafe, рекомендованный к использованию API, решающий поставленную задачу. В этой ситуации ты можешь привести хоть один довод за манипуляцию номерами фреймов?

Нормальных, рекомендованных к использованию API в случае линукса быть вообше не может. Это просто ещё одно мнение разработчика. А то, что мнений много, и очень часто они не совпадают, можно удостовериться по любому обсуждению в lkml.

Зачем? Чтобы доказать, что ты круче Рубини и Хартмана? Я лично предпочитаю не идти против учебников без веских причин.

Посмотри цитату, которую я привел. Формально их заветы полностью применимы в случае remap_pfn_range. У vm_insert_page никаких преимуществ в этом плане нет. Принцип тоже один и тот же, да и не может он быть разным. Таблица страниц, vma. Может быть, все-таки, ты можешь показать принципиальную разницу между ними?

Что касается учебников, то к ним на мой взгляд с определенного момента аппелировать нельзя. Нужно изучать код. Учебники пишутся слишком для общих случаев и, как правило, для писателей драйверов (прикладных программистов ядерного уровня). Потому что авторов замучили элементарными вопросами, и поэтому они старательно формулируют подходы максимально их исключающие.

Кто такой Рубини не знаю, с Хартманом один раз переписывался.

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

> Нормальных, рекомендованных к использованию API в случае линукса быть вообше не может.

Твое мнение понятно и совершенно не совпадает с моим.

Может быть, все-таки, ты можешь показать принципиальную разницу между ними?

Содержимое таблицы страниц будет одинаковым, наверное. Ты это считаешь принципиальным?

Кто такой Рубини не знаю

Автор LDD и LDD2, соавтор LDD3.

tailgunner ★★★★★
()
Ответ на: комментарий от Artem-Dnepr

> Реально хотелосьбы раз в 10 больше. Грубо говоря, в ядре - реалтайм. В юзер-спейс очень мягкий реалтайм, чтобы состыковать их нужен буфер который проглотит раскручивание HDD после слипа, тормоза флешки итд.

Ну понятно, у меня примерно такая же задача.

При первом обращении падает.

Тогда попробуй get_free_pages. Это единственная разница, которую я вижу с кодом, который у меня работает.

tailgunner ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.