Взаимодействие разветвления и памяти пользовательского пространства, отображенное в ядре

StackOverflow https://stackoverflow.com/questions/4046192

Вопрос

Рассмотрим драйвер Linux, который использует get_user_pages (или get_page) для сопоставления страниц вызывающего процесса.Физический адрес страниц затем передается на аппаратное устройство.И процесс, и устройство могут читать и записывать страницы, пока стороны не решат прекратить связь.В частности, связь может продолжать использовать страницы после системного вызова, вызывающего get_user_pages возвращается.Системный вызов фактически устанавливает зона общей памяти между процессом и аппаратным устройством.

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

  1. что ядру нужно сделать, чтобы защититься от потенциально некорректного процесса (я не хочу создавать безопасность дыра!);
  2. каким ограничениям должен подчиняться процесс, чтобы функциональность нашего драйвера работает корректно (т.е.физическая память остается отображенной по тому же адресу в родительском процессе).

    • В идеале мне бы хотелось, чтобы дочерний процесс вообще не использовал наш драйвер (он, вероятно, вызывает exec почти сразу) на работу.
    • В идеале родительский процесс не должен предпринимать никаких специальных действий при выделении памяти, поскольку у нас есть существующий код, который передает буфер, выделенный стеком, драйверу.
    • я знаю madvise с MADV_DONTFORK, и было бы нормально, если бы память исчезла из пространства дочернего процесса, но это неприменимо к буферу, выделенному в стеке.
    • «Не используйте fork, пока у вас активно соединение с нашим драйвером» — это раздражает, но приемлемо в крайнем случае, если соблюден пункт 1.

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

Версия ядра не полностью исправлена, но является последней (скажем, ≥2.6.26).Мы нацелены только на платформы Arm (пока однопроцессорные, но многоядерные не за горами), если это имеет значение.

Это было полезно?

Решение

А fork() не будет мешать get_user_pages(): get_user_pages() дам тебе struct page.

Вам нужно будет kmap() прежде чем получить к нему доступ, и это сопоставление выполняется в пространстве ядра, а не в пространстве пользователя.

РЕДАКТИРОВАТЬ: get_user_pages() коснитесь таблицы страниц, но вам не следует об этом беспокоиться (он просто проверяет, что страницы отображаются в пространстве пользователя), и возвращает -EFAULT, если при этом возникли какие-либо проблемы.

Если вы fork(), пока не будет выполнено копирование при записи, дочерний элемент сможет видеть эту страницу.Как только копирование при записи будет выполнено (поскольку дочерний/драйвер/родитель написал на страницу через сопоставление пользовательского пространства, а не через ядро ​​kmap(), которое есть у драйвера), эта страница больше не будет использоваться совместно.Если вы все еще удерживаете kmap() на странице (в коде драйвера), вы не сможете узнать, удерживаете ли вы родительскую страницу или дочернюю.

1) Это не дыра в безопасности, потому что как только вы выполните execve(), все это исчезнет.

2) Когда вы выполняете fork(), вы хотите, чтобы оба процесса были идентичными (это форк !!).Я думаю, что ваш дизайн должен позволять как родителю, так и ребенку иметь доступ к драйверу.Execve() очистит все.

А как насчет добавления некоторых функций в пользовательское пространство, например:

 f = open("/dev/your_thing")
 mapping = mmap(f, ...)

Когда на вашем устройстве вызывается mmap(), вы устанавливаете сопоставление памяти со специальными флагами:http://os1a.cs.columbia.edu/lxr/source/include/linux/mm.h#071

У вас есть интересные вещи, например:

#define VM_SHARED       0x00000008
#define VM_LOCKED       0x00002000
#define VM_DONTCOPY     0x00020000      /* Do not copy this vma on fork */

VM_SHARED отключит копию на записи vm_locked отключить обмен на этой странице VM_DONTCOPY сообщит ядру не копировать регион VMA на вилке, хотя я не думаю, что это хорошая идея

Другие советы

Короткий ответ: использовать madvise(addr, len, MADV_DONTFORK) в любых буферах пользовательского пространства, которые вы передаете своему драйверу.Это сообщает ядру, что сопоставление не должно копироваться от родительского элемента к дочернему, поэтому CoW отсутствует.

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

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

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

Другой способ избежать CoW — создать общее сопоставление, но по очевидным причинам вы не можете сопоставить общий стек.

Это означает, что вы рискуете получить CoW, если сделаете форк.Даже если дочерний элемент «просто» выполняется, он все равно может коснуться страницы стека и вызвать CoW, что приведет к тому, что родительский элемент получит другую страницу, что плохо.

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

Я думаю, вы действительно хотите, чтобы вся память, выделяемая вашему драйверу, поступала из специального распределителя в пользовательском пространстве.Это не должно быть так навязчиво.Распределитель может либо mmap ваше устройство напрямую, как предлагалось в другом ответе, или просто используйте анонимный mmap, madvise(DONTFORK), и, вероятно, mlock() чтобы избежать замены.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top