Память, на которую ссылаются user_addr
(или p1
) и p2
, будет одними и теми же страницами физической памяти после того, как они будут фактически закреплены в физической памяти get_user_pages()
. (До вызова get_user_pages()
страницы могли еще не находиться в физической памяти.) Однако user_addr
(и p1
) — это адрес страницы в пространстве пользователя, а p2
— это адрес страницы в пространстве ядра. kmap()
создаст временное сопоставление страницы физической памяти с пространством ядра.
В arm64 (а также в amd64), если бит 63 обрабатывается как бит знака, то адреса пространства пользователя неотрицательны, а адреса пространства ядра отрицательны. Таким образом, числовые значения адресов пространства пользователя и пространства ядра не могут быть равными.
Большая часть кода ядра не должна напрямую разыменовывать указатели пользовательского пространства. Следует использовать функции и макросы доступа к памяти пользовательского пространства и проверять их на наличие сбоев. Первая часть вашего примера должна быть примерно такой:
int __user *p1 = (int __user *)user_addr;
if (put_user(1, p1))
return -EFAULT;
pr_info("kernel: first: 0x%lx\n", (unsigned long)p1);
put_user()
вернет 0 в случае успеха или -EFAULT
в случае неудачи.
get_user_pages()
вернет либо количество страниц, закрепленных в памяти, либо отрицательное значение ошибки, если ни одна из запрошенных страниц не может быть закреплена. (Он вернет 0
только в том случае, если количество запрошенных страниц равно 0.) Количество фактически закрепленных страниц может быть меньше запрошенного, но, поскольку ваш код запрашивает только одну страницу, возвращаемое значение в этом случае будет либо 1
или отрицательное значение ошибки. Вы можете использовать переменную для захвата номера ошибки. Обратите внимание, что он должен вызываться с заблокированным семафором mmap текущей задачи:
#define NR_REQ 1
struct page *pages[NR_REQ];
long nr_gup;
mmap_read_lock(current->mm);
nr_gup = get_user_pages(user_addr, NR_REQ, FOLL_WRITE, pages, NULL);
mmap_read_unlock(current->mm);
if (nr_gup < 0)
return nr_gup;
if (nr_gup < NR_REQ) {
/* Some example code to deal with not all pages pinned - just 'put' them. */
long i;
for (i = 0; i < nr_gup; i++)
put_page(pages[i]);
return -ENOMEM;
}
Примечание. Вместо get_user_pages()
можно использовать get_user_pages_fast()
. Если используется get_user_pages_fast()
, вызовы mmap_read_lock()
и mmap_read_unlock()
выше должны быть удалены:
#define NR_REQ 1
struct page *pages[NR_REQ];
long nr_gup;
nr_gup = get_user_pages_fast(user_addr, NR_REQ, FOLL_WRITE, pages);
if (nr_gup < 0)
return nr_gup;
if (nr_gup < NR_REQ) {
/* Some example code to deal with not all pages pinned - just 'put' them. */
long i;
for (i = 0; i < nr_gup; i++)
put_page(pages[i]);
return -ENOMEM;
}
kmap()
временно отобразит страницу в адресное пространство ядра. Он должен быть связан с вызовом kunmap()
для освобождения временного сопоставления:
p2 = kmap(pages[0]);
/* do something with p2 here ... */
kunmap(p2);
Страницы, закрепленные с помощью get_user_pages()
, необходимо «поместить» с помощью put_page()
, когда закончите. Если они были записаны, их сначала нужно пометить как «грязные» с помощью set_page_dirty_lock()
. Последняя часть вашего примера должна выглядеть примерно так:
p2 = kmap(pages[0]);
*p2 = 2; /* this also works */
pr_info("kernel: second: 0x%lx\n", (unsigned long)p2);
kunmap(p2);
set_page_dirty_lock(pages[0]);
put_page(pages[0]);
Приведенный выше код не является полностью надежным. Указатель p2
может быть смещен для *p2
разыменования, или *p2
может выходить за границу страницы. Надежный код должен справляться с такими ситуациями.
Поскольку доступ к памяти через адреса пользовательского пространства необходимо выполнять с помощью специальных функций и макросов доступа к пользовательскому пространству, они могут засыпать из-за ошибок страниц (если только страницы не были заблокированы в физической памяти) и действительны только (если вообще) в рамках одного процесса блокировка области адресов пользовательского пространства в памяти с помощью get_user_pages()
и сопоставление страниц с адресным пространством ядра (при необходимости) полезно в некоторых обстоятельствах. Это позволяет получить доступ к памяти из произвольного контекста ядра, такого как обработчик прерывания. Это позволяет выполнять массовое копирование в и из отображаемого в память ввода-вывода (memcpy_toio()
или memcpy_fromio()
). Операции прямого доступа к памяти могут выполняться с пользовательской памятью после того, как она будет заблокирована get_user_pages()
. В этом случае страницы будут сопоставлены с адресами DMA API DMA.
person
Ian Abbott
schedule
17.08.2020