LINUX.ORG.RU

Записать данные в userspace из потока ядра


0

1

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


Ответ на: комментарий от eXire

copy_to_user()
copy_from_user()
Копируют из/в кернельный буфер. А мне хочется избежать изпользования промежуточного буффера.

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

> get_user_pages()

или, драйвер реализует mmap в kernel buffer.

idle ★★★★★
()

remap_pfn_range() же

grep -R remap_pfn_range ./drivers

Ищи пример с памятью, а не регистрами устройства. Там такой есть, примерно между серединой и концом.

ttnl ★★★★★
()

Если речь о DMA, то get_user_pages, dma_map_* (или dma_map_sg_*) и специфичный для устройства запуск DMA.

Documentation/DMA-{API,mapping}.txt и соответствующая глава LDD.

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

Сделал через get_user_pages(). Но временами, при чтении большого обьема (несколько метров) возникает ошибка доступа: unable to handle kernel paging request at virtual address.

Код примерно такой:
read(..., char *to, size_t size,... ) {
   handler->task = current;
   handler->buf = to;
   handler->size = size;
   kernel_thread( handler );
}

kernel_thread( *handler ) {
   struct mm_struct *mm = get_task_mm( handler->task );
   down_write( &mm->mmap_sem );
   get_user_pages( handler->task, mm, handler->buf, (handler->size + PAGE_SIZE-1)/PAGE_SZIE , 1, 0, NULL, NULL );
   driver_read( ..., handler->buf, handler->size, ... );
   up_write( &mm->mmap_sem );
}

Ошибка возникает в driver_read() и не каждый раз.

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

Тут не совсем драйвер. Точнее драйвер не один и не только драйвер. Есть процесс - арбитр (тот самый kernel thread), через который эти драйвера обмениваются друг с другом данными, так как данные не только драйверские но и из сети. Сейчас хочу реализовать чтение из этого арбитра в пользовательский процесс. Память в арбитре выделяется сейчас обычным kmalloc(...,GFP_KERNEL).

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

> Тут не совсем драйвер. Точнее драйвер не один и не только драйвер. Есть процесс - арбитр (тот самый kernel thread), через который эти драйвера обмениваются друг с другом данными

Либо у тебя необычная задача (и тогда задавать простые вопросы бесполезно), либо у тебя бессмысленно сложная архитектура. Но я в любом случае не понимаю, почему ты не хочешь воспользоваться copyto_user.

А перемежающийся «unable to handle kernel paging request at virtual address» означает, что ты делаешь что-то очень неправильное.

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

>remap_pfn_range() он же реммапит kernel адресс на юзерспейс. А мне наоборот надо

Тебе же никто не запрещает выделить память в ядре, а потом перемапировать её в userspace. Это намного изящнее.

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

Copy_to/from_user работают замечательно, только требуют двойного выделения памяти:
userspace_read(..., buf, size ) -> kernel_read, kbuf=kmalloc( size, ...) -> арбитр, запись в kbuf -> kernel_read, copy_to_user( buf, kbuf, ... ), kfree( kbuf) -> userspace.

Хочется обойтись без использования промежуточного kbuf .

Ну архитектура, какая есть :-(

Вот и я пытаюсь понять, что не правильно делаю.

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

Мне как раз хочется избежать выделения памяти в ядре. Так как она уже выделяется userspaceом.

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

> Хочется обойтись без использования промежуточного kbuf .

Кстати, не думаю, что mmap тебе поможет

Вот и я пытаюсь понять, что не правильно делаю.

Без понимания твоей архитектуры вряд ли тебе ответят.

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

Архитектура, в кратце, такая:
Есть процесс арбитр, запущенный как поток ядра. Он обслуживает несколько устройств, притом из одного устройства можно писать в другой минуя userspace посредством этого арбитра. Устройства могут быть разные.
Примерно так:
Устройство1 записало данные в свой буффер, сообщилу арбитру что у него есть данные. Арбитр берёт эти данные и передаёт в другое устройство, в устройство1 передаёт новый буфер для записи данных.
Также можно читать данные из устройства1 в userspace:
Устройство1 записало данные в свой буффер, сообщилу арбитру что у него есть данные. Арбитр берёт эти данные и передаёт в другое устройство и __копирует в userspace__.
Вот на этапе __копирует в userspace__ хочется обойтись одним буфером, выделенным в юзерспейсе, без копирования в промежуточный буфер.

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

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

> Устройство1 записало данные в свой буффер, сообщилу арбитру что у него есть данные. Арбитр берёт эти данные и передаёт в другое устройство и __копирует в userspace__.

И чем же тебе не нравится copy_to_user в этом случае? Копируй из буфера устройства в юзерспейс. Где тут двойное копирование?

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

Надеюсь, у тебя есть серьезные причины для этого :) Потому что внос в юзерспейс просто напрашивается.

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

>Мне как раз хочется избежать выделения памяти в ядре. Так как она уже выделяется userspaceом.

Ты не понял. В userspace выделять память не нужно. Выделяешь один раз в ядре общий буфер. В userspace делаешь mmap() на этот буфер и после этого можешь работать с ним как из ядра, так и из приложения.

В том куске, что ты выше привел, сейчас вообще ахтунг написан. При каждом read создается поток ядра и перебирается таблица страниц. И ещё ты не проверяешь get_user_pages на возвращаемое значение.

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

И чем же тебе не нравится copy_to_user в этом случае? Копируй из буфера устройства в юзерспейс. Где тут двойное копирование?

Процесс арбитр не имеет доступа к userspace так как поток ядра. А юзерский процесс вызваший read не знате о буфере устройства (этот буфер может быть даже еше не готов). Сейчас юзерский процесс сообщает арбитру, чтоб данные не только передал следущему устройству, но и скопировал в userspace. Конечно можно из арбитра вызвать юзерский процесс, находящийся в read, и сообщить ему где находиться буфер, но это еще кривее чем писать в юзерспейс из арбитра, плюс вносит дополнительные задержки.

Надеюсь, у тебя есть серьезные причины для этого :) Потому что внос в юзерспейс просто напрашивается.

И представь что станет с производительностью, если, к примеру 2 pci устройства, будут общаться друг с другом через userspace.

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

>Ты не понял. В userspace выделять память не нужно. Выделяешь один раз в ядре общий буфер. В userspace делаешь mmap() на этот буфер и после этого можешь работать с ним как из ядра, так и из приложения.
Тут проблема в том, что юзерспейс не мой, и, в общем случае, стандартный, делающий обычный read в свой буфер: cat, dd и т.д.

В том куске, что ты выше привел, сейчас вообще ахтунг написан. При каждом read создается поток ядра и перебирается таблица страниц. И ещё ты не проверяешь get_user_pages на возвращаемое значение.

Не, поток ядра уже есть и работает, просто ему сообщается что данные нужно так же записать и в юзерский буфер.
Проверка get_user_pages на возвращаемое значение конечно есть, просто здесь не приведена дабы не засарять тред.
Вот переборки таблицы страниц мне тоже хочется избежать. Можно ли заммапать vma из юзерского процесса к vma кернельного?

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

> Процесс арбитр не имеет доступа к userspace так как поток ядра.

Всё он имеет.

юзерский процесс вызваший read не знате о буфере устройства (этот буфер может быть даже еше не готов). Сейчас юзерский процесс сообщает арбитру, чтоб данные не только передал следущему устройству, но и скопировал в userspace.

Какой ахтунг. Что тебе мешает «сообщить арбитру», а потом читать непосредственно с устройства?

И представь что станет с производительностью, если, к примеру 2 pci устройства, будут общаться друг с другом через userspace.

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

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

>Всё он имеет.
Как? Собственно это и есть главный вопрос треда.

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

Собственно говоря, как получить доступ к юзерскому буферу не имея пользовательского контекста?


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

>Вот переборки таблицы страниц мне тоже хочется избежать. Можно ли заммапать vma из юзерского процесса к vma кернельного?

У ядерного потока нет mm_struct и vma, он использует mm_struct предыдущего исполнявшегося пользовательского процесса. Но это тебе и не нужно. Когда ты делаешь get_user_pages, страницы «закрепляются», и ты можешь обращаться к ним обычным способом.

Тут проблема в том, что юзерспейс не мой, и, в общем случае, стандартный, делающий обычный read в свой буфер: cat, dd и т.д.

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

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

>> Одно устройство пишет в юзерспейсный буфер, другое читает оттуда же, арбитр в ядре просто не нужен. Я не говорю, что эта схема тебе подъходит, но и не вижу и противопоказаний.

Собственно говоря, как получить доступ к юзерскому буферу не имея пользовательского контекста?

Ыыы... итак, схема такова: программа читает с устройства1 в свой буффер, посредством обычного read; после завершения чтения она пишет из устройство2 из этого же буфера; таким образом, у функций чтения и записи всегда есть пользовательский контекст. В качестве дополнительного бонуса ты избавляешься от арбитра в ядре.

Даже если требования по латентности у тебя не позволяют использовать такую схему, то всё равно программа должна читать с утройства1 непосредственно, без всяких арбитров.

А если тебе нужен обязательно доступ к произвольному контексту (хотя он тебе не нужен), ты ССЗБ и должен сам грепать код ядра в поисках примеров.

P.S. poll добавлять по вкусу.

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

> У ядерного потока нет mm_struct и vma, он использует mm_struct предыдущего исполнявшегося пользовательского процесса. Но это тебе и не нужно. Когда ты делаешь get_user_pages, страницы «закрепляются», и ты можешь обращаться к ним обычным способом.
Ну вот получается, что с ними не всегда можно общаться обычным способом, иначе не было бы «unable to handle kernel paging request...»
Можно ли прикрепить mm_struct и vma из пользовательского процесса в ядерному (предварительно создав конечно же)?

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

Ну что бы освободить страницу, dd нужно сначала выйти из read, а до того как dd выйдет, я уже освобожу страницы (тут правда не понятно насчёт сигналов и не блокирующего io)

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

> Ыыы... итак, схема такова: программа читает с устройства1 в свой буффер, посредством обычного read; после завершения чтения она пишет из устройство2 из этого же буфера; таким образом, у функций чтения и записи всегда есть пользовательский контекст. В качестве дополнительного бонуса ты избавляешься от арбитра в ядре.
Даже если требования по латентности у тебя не позволяют использовать такую схему, то всё равно программа должна читать с утройства1 непосредственно, без всяких арбитров.
А если тебе нужен обязательно доступ к произвольному контексту (хотя он тебе не нужен), ты ССЗБ и должен сам грепать код ядра в поисках примеров.

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

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

>Ну вот получается, что с ними не всегда можно общаться обычным способом, иначе не было бы «unable to handle kernel paging request...»

Нужно обращаться по правильным адресам, тогда такое не случается.

Если это нижняя память, то она уже примаппирована и page_address() тебе в помощь. Ели верхняя — создаешь временное маппирование и то же самое.

Можно ли прикрепить mm_struct и vma из пользовательского процесса в ядерному (предварительно создав конечно же)?

Нет

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

Нужно обращаться по правильным адресам, тогда такое не случается.

Если это нижняя память, то она уже примаппирована и page_address() тебе в помощь. Ели верхняя — создаешь временное маппирование и то же самое. Память у меня всегда нижняя, так как её всего 128 метров (сейчас запускается в qemu i386, в реальности будет arm)

Сейчас сделал так:

read(..., char *to, size_t size,... ) {
   handler->task = current;
   handler->buf = to;
   handler->size = size;
   kernel_thread( handler );
}

kernel_thread( *handler ) {
   struct mm_struct *mm = get_task_mm( handler->task );
   current->mm = mm;
   down_read( &mm->mmap_sem );
   nr_pages = (handler->size + PAGE_SIZE-1)/PAGE_SIZE;
   ret = get_user_pages( handler->task, mm, handler->buf, nr_pages, 1, 1, NULL, NULL );
   if( ret < nr_pages )
      goto error;
   driver_read( ..., handler->buf, handler->size, ... );
   up_read( &mm->mmap_sem );
   current->mm = NULL;
   mput( mm );
}

Падать перестало. Но делать current->mm = mm; как-то очень не хорошо. Подозреваю, что в vm_ops для mm->vma надо изменить обработчик nopage

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

> это должно работать уже на этапе инициализации (до инита)

Странный аргумент. Впрочем, я уже пас :)

без арбитра не обойтись.

В том, что ты так думаешь, и корень проблем.

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

В ф-ции driver_read() ты обращаешься по пользовательским адресам. А нужно обращаться по ядерным.

Ядерные адреса нужно получать из функции get_user_pages, передавая массив указателей на страницы в качестве 7-го аргумента. Потом конвертировать страницы в виртуальные адреса с помощью page_address().

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

О, заработало, спасибо! Но, теперь, как я понимаю, читать надо постранично?!

get_user_pages( handler->task, mm, handler->buf, nr_pages, 1, 1, pages, NULL );
for( i=0; i < nr_pages; i++ )
   driver_read( kmap( [pages[i] )... );
ZDE
() автор топика
Ответ на: комментарий от ZDE

>О, заработало, спасибо! Но, теперь, как я понимаю, читать надо постранично?!

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

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

> Можно ли прикрепить mm_struct и vma из пользовательского

процесса в ядерному


справедливости ради, сделать это можно: use_mm/unuse_mm.

но, как уже обьяснили, не нужно.

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

>Но, теперь, как я понимаю, читать надо постранично?!

Можно попробовать их отобразить в памяти ядра линейно через vmap или аналоги, но проще и намного переносимее с помощью wrapper обработать постранично.

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