2009-03-15 3 views
41

Я пытаюсь получить доступ к физической памяти непосредственно для встроенного проекта Linux, но я не уверен, как лучше всего назначить память для моего использования.Прямой доступ к памяти в Linux

Если я регулярно загружаю свое устройство и получаю доступ к/dev/mem, я могу легко читать и писать практически в любом месте, где захочу. Однако в этом я получаю доступ к памяти, которая может быть легко распределена для любого процесса; которые я не хочу делать

Мой код/​​DEV/МЕМ (все проверки ошибок и т.д. удалены):

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 показывает, что он умирает на уступках, хотя в БГДЕ, я не могу разыменование адреса я получаю от ттара (хотя 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; 
} 
+0

Чтобы уточнить, вы хотите (в модуле) вернуть адресное пространство в пользовательское пространство, полученное через vmalloc(), а не kmalloc(), правильно? Сколько памяти вам действительно нужно? –

+0

Это, вероятно, проще всего с помощью kmalloc(), то, что вы делаете, это установить 1,5 МБ пространства ядра и представить его в пользовательском пространстве. Если это то, что вы хотите сделать, я освежусь на нескольких ядрах и попытаюсь ответить. –

+0

Обратите внимание, что делать это с помощью vmalloc() может быть очень неприятной задачей. Сумма, которую вы на самом деле должны отображать, влияет на ответ, поэтому вы уверены, что его 1,5 МБ или меньше? –

ответ

13

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

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

Возврат к linux device drivers в формате PDF. Взгляните на главу 15, она описывает эту технику на стр. 443

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

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

Edit: remap_pfn_range ioremap возвращает virtual_adress, который необходимо преобразовать в PFN для remap_pfn_ranges. Теперь, я не совсем понимаю, что такое PFN (номер страницы Frame), но я думаю, что вы можете получить позвонив

virt_to_phys(pt) >> PAGE_SHIFT 

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

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

+0

Когда вы говорите: «Я думаю, вы должны зарезервировать некоторую память во время загрузки, то есть зарезервировать некоторую физическую память, чтобы она не была затронута ядром». Вы имеете в виду загрузку с mem = XXXm, где XXX меньше, чем фактический адрес? Это то, о чем я изначально думал. – Mikeage

+0

[cont] Я вижу код там, используя ioremap(); Я попробую это. Итак, чтобы подтвердить: загрузитесь с mem = XXX, ioremap (XXX + 1), а затем, что лучший способ перевести это на адрес пользовательского пространства? 1 – Mikeage

+0

Hrmmm. Я попробовал это и реализовал mmap следующим образом (вся ошибка проверки удалена): static int foo_mmap (struct file * filp, struct vm_area_struct * vma) { remap_pfn_range (vma, vma-> vm_start, (unsigned long) pt, vma-> vm_end - vma-> vm_start, PAGE_SHARED); } – Mikeage

12

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

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

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

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

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

+0

это встроенная система; Я не беспокоюсь о селинках. Что касается остальной части вашего Q, я сейчас добавлю некоторые подробности. – Mikeage

0

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

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

+0

Параллелизм может быть проблемой, особенно при использовании DM, который не соблюдает барьеры записи. Вопрос (несмотря на изменения) действительно неоднозначен, как они фактически будут использовать такой регион с таким интерфейсом. –

+0

Кроме того, (ссылка на мой первый комментарий) может возникнуть проблема с кэшем записи, особенно при заказе. –

+0

Ramdisk не будет работать; см. мое последнее редактирование – Mikeage

2

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

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

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

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

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

Linux Device Drivers

Understanding the Linux Kernel

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

0

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

+0

Я действительно использовал memmap [или просто mem = XXm @ YY, которое мое ядро ​​поддерживает для резервирования блока посередине.] Открытая проблема заключалась в том, как напрямую обращаться к памяти. – Mikeage

Смежные вопросы