Прямой доступ к памяти в Linux - PullRequest
43 голосов
/ 15 марта 2009

Я пытаюсь получить доступ к физической памяти напрямую для проекта встроенного 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;
}

Ответы [ 5 ]

15 голосов
/ 16 марта 2009

Я думаю, вы можете найти много документации по части kmalloc + mmap. Тем не менее, я не уверен, что вы можете kmalloc так много памяти непрерывным образом, и всегда иметь его в одном и том же месте. Конечно, если все всегда одинаково, то вы можете получить постоянный адрес. Однако каждый раз, когда вы меняете код ядра, вы получаете другой адрес, поэтому я бы не стал использовать решение kmalloc.

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

Это возвращает нас к драйверам устройств linux в формате PDF. Посмотрите на главу 15, она описывает эту технику на странице 443

Редактировать: ioremap и mmap. Я думаю, что это может быть легче отладить, выполняя действия в два этапа: сначала получите ioremap правильно, и протестируйте его, используя символьную операцию устройства, то есть чтение / запись. Как только вы узнаете, что можете безопасно получить доступ ко всей памяти ioremapped с помощью чтения / записи, вы попытаетесь отобразить весь диапазон ioremapped.

А если у вас возникли проблемы, возможно, напишите еще один вопрос о mmaping

Редактировать: remap_pfn_range ioremap возвращает виртуальный_адрес, который вы должны преобразовать в pfn для remap_pfn_ranges. Сейчас я не совсем понимаю, что такое pfn (номер фрейма страницы), но я думаю, что вы можете получить один вызов

virt_to_phys(pt) >> PAGE_SHIFT

Это, вероятно, не правильный путь (тм), чтобы сделать это, но вы должны попробовать это

Вам также следует проверить, что FOO_MEM_OFFSET - это физический адрес вашего блока памяти. То есть, прежде чем что-то случится с MMU, ваша память будет доступна в 0 на карте памяти вашего процессора.

14 голосов
/ 15 марта 2009

Извините, что отвечаю, но не совсем отвечаю, я заметил, что вы уже редактировали вопрос. Обратите внимание, что SO не уведомляет нас, когда вы редактируете вопрос. Здесь я даю общий ответ, когда вы обновляете вопрос, пожалуйста, оставьте комментарий, затем я отредактирую свой ответ.

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

Раскрыть предыдущее легко, разоблачение последнего может быть проблемой в тылу с интерфейсом, который вы описываете по мере необходимости. Вы отметили, что 1,5 МБ - приблизительная оценка того, сколько вам на самом деле нужно зарезервировать, это железное покрытие? Т.е. тебе удобно брать это из пространства ядра? Можете ли вы адекватно справиться с ENOMEM или EIO из пользовательского пространства (или даже с диском)? А что же в этот регион?

Кроме того, параллелизм будет проблемой с этим? Если так, вы собираетесь использовать futex? Если ответ на любой из них «да» (особенно последний), вполне вероятно, что вам придется прикусить пулю и пойти с vmalloc() (или рискнуть гниением ядра изнутри). Кроме того, если вы даже ДУМАЕТЕ об интерфейсе ioctl() с символьным устройством (особенно для какой-то особой идеи блокировки), вы действительно хотите использовать vmalloc().

Кроме того, вы прочитали это ? Кроме того, мы даже не касаемся того, что grsec / selinux подумает об этом (если он используется).

3 голосов
/ 15 марта 2009

/ dev / mem подходит для простых подсчетов и подсчетов в реестре, но как только вы перейдете к прерываниям и территории DMA, вам действительно следует написать драйвер режима ядра. То, что вы делали для своих предыдущих ОС без управления памятью, просто не прививается хорошо для ОС общего назначения, такой как Linux.

Вы уже думали о проблеме выделения буфера DMA. Теперь подумайте о прерывании "DMA done" с вашего устройства. Как вы собираетесь установить программу обработки прерываний?

Кроме того, / dev / mem обычно заблокирован для пользователей без полномочий root, поэтому он не очень удобен для общего использования. Конечно, вы можете изменить его, но затем вы открыли большую дыру в безопасности системы.

Если вы пытаетесь сохранить базу кода драйвера одинаковой для разных операционных систем, вам следует рассмотреть возможность ее рефакторинга на отдельные уровни режима пользователя и ядра с промежуточным интерфейсом, подобным IOCTL. Если вы пишете часть пользовательского режима как общую библиотеку кода на C, то будет легко портировать между Linux и другими операционными системами. Специфичная для ОС часть - это код режима ядра. (Мы используем такой подход для наших водителей.)

Кажется, вы уже пришли к выводу, что пришло время написать драйвер ядра, так что вы на правильном пути. Единственный совет, который я могу добавить, - читать эти книги от корки до корки.

Драйверы для устройств Linux

Понимание ядра Linux

(Имейте в виду, что эти книги около 2005 года, поэтому информация немного устарела.)

1 голос
/ 28 октября 2009

Вы смотрели на параметр ядра 'memmap'? На i386 и X64_64 вы можете использовать параметр memmap, чтобы определить, как ядро ​​будет обрабатывать очень специфические блоки памяти (см. Документацию параметр ядра Linux ). В вашем случае вы бы хотели пометить память как «зарезервированную», чтобы Linux вообще ее не трогал. Затем вы можете написать свой код, чтобы использовать этот абсолютный адрес и размер (горе вам, если вы выйдете за пределы этого пространства).

1 голос
/ 15 марта 2009

Я, безусловно, не эксперт по этим вопросам, так что это будет вопрос к вам, а не ответ. Есть ли какая-то причина, по которой вы не можете просто создать небольшой раздел RAM-диска и использовать его только для своего приложения? Разве это не даст вам гарантированный доступ к той же части памяти? Я не уверен, что возникнут какие-либо проблемы с производительностью ввода-вывода или дополнительные издержки, связанные с этим. Это также предполагает, что вы можете сказать ядру разделить определенный диапазон адресов в памяти, не будучи уверенным, возможно ли это.

Я прошу прощения за новый вопрос, но я нашел ваш вопрос интересным, и мне любопытно, можно ли таким образом использовать RAM-диск.

...