Подтвердить что ты не робот

Прямой доступ к памяти в 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;
}
4b9b3361

Ответ 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 не касался ее вообще. Затем вы можете написать свой код, чтобы использовать этот абсолютный адрес и размер (горе вам, если вы выйдете за пределы этого пространства).