Прямой доступ к памяти в Linux
Я пытаюсь получить доступ к физической памяти непосредственно для встроенного проекта Linux, но я не уверен, как я могу наилучшим образом обозначить память для моего использования.
Если я регулярно загружаю свое устройство и получаю доступ к /dev/mem, я могу легко читать и писать практически в любом месте, где захочу. Однако в этом я получаю доступ к памяти, которая может быть легко распределена для любого процесса; который я не хочу делать
Мой код для /dev/mem (все проверки ошибок и т.д. удалены):
mem_fd = open("/dev/mem", O_RDWR));
mem_p = malloc(SIZE + (PAGE_SIZE - 1));
if ((unsigned long) mem_p % PAGE_SIZE) {
mem_p += PAGE_SIZE - ((unsigned long) mem_p % PAGE_SIZE);
}
mem_p = (unsigned char *) mmap(mem_p, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, mem_fd, BASE_ADDRESS);
И это работает. Тем не менее, я бы хотел использовать память, которую никто другой не коснется. Я попытался ограничить объем памяти, который ядро видит при загрузке с mem = XXXm, а затем установил BASE_ADDRESS на что-то выше этого (но ниже физической памяти), но он, похоже, не постоянно обращается к одной и той же памяти.
Основываясь на том, что я видел в Интернете, я подозреваю, что мне может понадобиться модуль ядра (который в порядке), который использует либо ioremap(), либо remap_pfn_range() (или оба), но я абсолютно не знаю, как; может ли кто-нибудь помочь?
EDIT:
Я хочу, чтобы всегда иметь доступ к одной и той же физической памяти (скажем, 1,5 МБ) и отложить эту память, чтобы ядро не выделило ее ни на какой другой процесс.
Я пытаюсь воспроизвести систему, которую мы использовали в других операционных системах (без управления памятью), благодаря чему я мог выделить пространство в памяти через компоновщик и получить к нему доступ, используя что-то вроде
*(unsigned char *)0x12345678
EDIT2:
Наверное, я должен предоставить более подробную информацию. Это пространство памяти будет использоваться для буфера ОЗУ для высокопроизводительного решения регистрации для встроенного приложения. В системах у нас нет ничего, что бы очистило или скремблировало физическую память во время мягкой перезагрузки. Таким образом, если я напишу бит на физический адрес X и перезагрузив систему, тот же бит будет по-прежнему установлен после перезагрузки. Это было протестировано на том же аппаратном обеспечении, на котором работает VxWorks (эта логика также хорошо работает в Nucleus RTOS и OS20 на разных платформах, FWIW). Моя идея заключалась в том, чтобы попробовать то же самое в Linux, обратившись к физической памяти напрямую; поэтому важно, чтобы я получал одинаковые адреса для каждой загрузки.
Я должен, вероятно, уточнить, что это для ядра 2.6.12 и новее.
EDIT3:
Здесь мой код, сначала для модуля ядра, затем для приложения пользовательского пространства.
Чтобы использовать его, я загружаюсь с mem = 95m, затем insmod foo-module.ko, затем mknod mknod/dev/foo c 32 0, затем запускает foo-user, где он умирает. Запуск под gdb показывает, что он умирает при назначении, хотя в gdb я не могу разыменовать адрес, который я получаю из mmap (хотя printf может)
Foo-module.c
#include <linux/module.h>
#include <linux/config.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <asm/io.h>
#define VERSION_STR "1.0.0"
#define FOO_BUFFER_SIZE (1u*1024u*1024u)
#define FOO_BUFFER_OFFSET (95u*1024u*1024u)
#define FOO_MAJOR 32
#define FOO_NAME "foo"
static const char *foo_version = "@(#) foo Support version " VERSION_STR " " __DATE__ " " __TIME__;
static void *pt = NULL;
static int foo_release(struct inode *inode, struct file *file);
static int foo_open(struct inode *inode, struct file *file);
static int foo_mmap(struct file *filp, struct vm_area_struct *vma);
struct file_operations foo_fops = {
.owner = THIS_MODULE,
.llseek = NULL,
.read = NULL,
.write = NULL,
.readdir = NULL,
.poll = NULL,
.ioctl = NULL,
.mmap = foo_mmap,
.open = foo_open,
.flush = NULL,
.release = foo_release,
.fsync = NULL,
.fasync = NULL,
.lock = NULL,
.readv = NULL,
.writev = NULL,
};
static int __init foo_init(void)
{
int i;
printk(KERN_NOTICE "Loading foo support module\n");
printk(KERN_INFO "Version %s\n", foo_version);
printk(KERN_INFO "Preparing device /dev/foo\n");
i = register_chrdev(FOO_MAJOR, FOO_NAME, &foo_fops);
if (i != 0) {
return -EIO;
printk(KERN_ERR "Device couldn't be registered!");
}
printk(KERN_NOTICE "Device ready.\n");
printk(KERN_NOTICE "Make sure to run mknod /dev/foo c %d 0\n", FOO_MAJOR);
printk(KERN_INFO "Allocating memory\n");
pt = ioremap(FOO_BUFFER_OFFSET, FOO_BUFFER_SIZE);
if (pt == NULL) {
printk(KERN_ERR "Unable to remap memory\n");
return 1;
}
printk(KERN_INFO "ioremap returned %p\n", pt);
return 0;
}
static void __exit foo_exit(void)
{
printk(KERN_NOTICE "Unloading foo support module\n");
unregister_chrdev(FOO_MAJOR, FOO_NAME);
if (pt != NULL) {
printk(KERN_INFO "Unmapping memory at %p\n", pt);
iounmap(pt);
} else {
printk(KERN_WARNING "No memory to unmap!\n");
}
return;
}
static int foo_open(struct inode *inode, struct file *file)
{
printk("foo_open\n");
return 0;
}
static int foo_release(struct inode *inode, struct file *file)
{
printk("foo_release\n");
return 0;
}
static int foo_mmap(struct file *filp, struct vm_area_struct *vma)
{
int ret;
if (pt == NULL) {
printk(KERN_ERR "Memory not mapped!\n");
return -EAGAIN;
}
if ((vma->vm_end - vma->vm_start) != FOO_BUFFER_SIZE) {
printk(KERN_ERR "Error: sizes don't match (buffer size = %d, requested size = %lu)\n", FOO_BUFFER_SIZE, vma->vm_end - vma->vm_start);
return -EAGAIN;
}
ret = remap_pfn_range(vma, vma->vm_start, (unsigned long) pt, vma->vm_end - vma->vm_start, PAGE_SHARED);
if (ret != 0) {
printk(KERN_ERR "Error in calling remap_pfn_range: returned %d\n", ret);
return -EAGAIN;
}
return 0;
}
module_init(foo_init);
module_exit(foo_exit);
MODULE_AUTHOR("Mike Miller");
MODULE_LICENSE("NONE");
MODULE_VERSION(VERSION_STR);
MODULE_DESCRIPTION("Provides support for foo to access direct memory");
Foo-user.c
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/mman.h>
int main(void)
{
int fd;
char *mptr;
fd = open("/dev/foo", O_RDWR | O_SYNC);
if (fd == -1) {
printf("open error...\n");
return 1;
}
mptr = mmap(0, 1 * 1024 * 1024, PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, fd, 4096);
printf("On start, mptr points to 0x%lX.\n",(unsigned long) mptr);
printf("mptr points to 0x%lX. *mptr = 0x%X\n", (unsigned long) mptr, *mptr);
mptr[0] = 'a';
mptr[1] = 'b';
printf("mptr points to 0x%lX. *mptr = 0x%X\n", (unsigned long) mptr, *mptr);
close(fd);
return 0;
}
Ответы
Ответ 1
Я думаю, вы можете найти много документации о части kmalloc + mmap.
Тем не менее, я не уверен, что вы можете kmalloc так много памяти смежным образом и всегда иметь одно и то же место. Конечно, если все всегда одно и то же, тогда вы можете получить постоянный адрес. Однако каждый раз, когда вы меняете код ядра, вы получите другой адрес, поэтому я не пошел бы с решением kmalloc.
Я думаю, что вы должны зарезервировать некоторую память во время загрузки, то есть зарезервировать некоторую физическую память, чтобы ядро не касалось ее. Тогда вы можете ioremap эту память, которая даст вам
виртуальный адрес ядра, а затем вы можете записать его и написать хороший драйвер устройства.
Возврат к драйверам устройств Linux в формате PDF. Взгляните на главу 15, она описывает этот метод на стр. 443
Изменить: ioremap и mmap.
Я думаю, что это может быть проще отладить, делая вещи в два шага: сначала получите ioremap
и проверить его с помощью операции с символьным устройством, то есть чтения/записи. Как только вы узнаете, что можете безопасно иметь доступ ко всей памяти ioremapped, используя чтение/запись, тогда вы пытаетесь изменить весь диапазон ioremapped.
И если у вас возникнут проблемы, может возникнуть еще один вопрос о mmaping
Изменить: remap_pfn_range
ioremap возвращает virtual_adress, который вы должны преобразовать в pfn для remap_pfn_ranges.
Теперь я не понимаю, что такое pfn (номер кадра страницы), но я думаю, вы можете получить один вызов
virt_to_phys(pt) >> PAGE_SHIFT
Возможно, это не правильный путь (tm), но вы должны попробовать его
Вы также должны проверить, что FOO_MEM_OFFSET является физическим адресом вашего блока RAM. Т.е. перед тем, как что-либо произойдет с mmu, ваша память будет доступна в 0 на карте памяти вашего процессора.
Ответ 2
Извините за ответ, но не совсем ответ, я заметил, что вы уже отредактировали вопрос. Обратите внимание, что SO не уведомляет нас, когда вы редактируете вопрос. Я даю общий ответ здесь, когда вы обновляете вопрос, пожалуйста, оставьте комментарий, затем я отредактирую свой ответ.
Да, вам нужно будет написать модуль. Речь идет о использовании kmalloc()
(выделение области в пространстве ядра) или vmalloc()
(выделение области в пользовательском пространстве).
Выявление предыдущего легко, разоблачение последнего может быть болью в тылу с видом интерфейса, который вы описываете по мере необходимости. Вы отметили, что 1,5 МБ - это приблизительная оценка того, сколько вам действительно нужно зарезервировать, это железо? Вам удобнее брать это из пространства ядра? Можете ли вы адекватно работать с ENOMEM или EIO из пользовательского пространства (или даже спящего диска)? IOW, что происходит в этом регионе?
Кроме того, будет concurrency проблема с этим? Если да, собираетесь ли вы использовать futex? Если ответ на вопрос "да" (особенно последний), его вероятность, что вам придется укусить пулю и пойти с vmalloc()
(или рисковать ядром изнутри). Кроме того, если вы даже ДУМАЕТ о интерфейсе ioctl()
к устройству char (особенно для какой-либо специальной идеи блокировки), вы действительно хотите пойти с vmalloc()
.
Кроме того, прочитали ли вы это? Плюс мы даже не касаемся того, что grsec/selinux будет думать об этом (если он используется).
Ответ 3
/dev/mem подходит для простых записок и pokes реестра, но как только вы переходите на прерывания и территорию DMA, вам действительно нужно написать драйвер режима ядра. То, что вы делали для своих ранее не управляемых оператором ОС, просто не прививается к ОС общего назначения, как Linux.
Вы уже подумали о проблеме выделения буфера DMA. Теперь подумайте о прерывании "DMA done" с вашего устройства. Как вы собираетесь установить службу обслуживания прерываний?
Кроме того, /dev/mem обычно заблокирован для пользователей без полномочий root, поэтому он не очень практичен для общего использования. Конечно, вы могли бы chmod, но тогда вы открыли большое отверстие безопасности в системе.
Если вы пытаетесь сохранить базовую базу кода драйвера аналогично между ОС, вам следует рассмотреть возможность реорганизации ее в отдельные слои пользователя и ядра с интерфейсом, подобным IOCTL. Если вы пишете часть пользовательского режима в качестве общей библиотеки кода C, ее легко переносить между Linux и другими ОС. Специфическая для ОС часть - это код режима ядра. (Мы используем такой подход для наших драйверов.)
Похоже, вы уже пришли к выводу, что пришло время написать драйвер ядра, так что вы на правильном пути. Единственный совет, который я могу добавить, - это прочитать эти книги по обложке.
Драйверы устройств Linux
Понимание ядра Linux
(Имейте в виду, что эти книги около 2005 года, поэтому информация немного устарела.)
Ответ 4
Я, безусловно, не специалист по этим вопросам, поэтому для вас это будет вопросом, а не ответом. Есть ли причина, по которой вы не можете просто создать небольшой раздел дискового диска и использовать его только для своего приложения? Не даст ли вам гарантированный доступ к одному и тому же фрагменту памяти? Я не уверен, что будут проблемы с производительностью ввода-вывода или дополнительные накладные расходы, связанные с этим. Это также предполагает, что вы можете указать ядру для разделения определенного диапазона адресов в памяти, не уверен, что это возможно.
Извиняюсь за новый вопрос, но я нашел ваш вопрос интересным, и мне любопытно, можно ли использовать диск RAM таким образом.
Ответ 5
Вы посмотрели на параметр ядра memmap? На i386 и X64_64 вы можете использовать параметр memmap, чтобы определить, как ядро будет передавать очень определенные блоки памяти (см. параметр ядра Linux). В вашем случае вы хотите пометить память как "зарезервированную", чтобы Linux не касался ее вообще. Затем вы можете написать свой код, чтобы использовать этот абсолютный адрес и размер (горе вам, если вы выйдете за пределы этого пространства).