Вопрос

Я пытаюсь получить прямой доступ к физической памяти для встроенного проекта 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() (или оба???), но я совершенно не знаю, как это сделать;может кто-нибудь помочь?

РЕДАКТИРОВАТЬ:Мне нужен способ всегда иметь доступ к одной и той же физической памяти (скажем, объемом 1,5 МБ) и выделять эту память, чтобы ядро ​​не выделяло ее какому-либо другому процессу.

Я пытаюсь воспроизвести систему, которая была у нас в других ОС (без управления памятью), с помощью которой я мог бы выделить пространство в памяти через компоновщик и получить к нему доступ, используя что-то вроде

*(unsigned char *)0x12345678

РЕДАКТИРОВАТЬ2:Думаю, мне следует предоставить более подробную информацию.Это пространство памяти будет использоваться в качестве буфера ОЗУ для высокопроизводительного решения для регистрации встроенного приложения.В наших системах нет ничего, что очищало бы или шифровало физическую память во время мягкой перезагрузки.Таким образом, если я запишу бит по физическому адресу X и перезагрузлю систему, тот же бит все равно будет установлен после перезагрузки.Это было протестировано на том же оборудовании, на котором работает VxWorks (эта логика также хорошо работает в Nucleus RTOS и OS20 на разных платформах, кстати).Моя идея заключалась в том, чтобы попробовать то же самое в Linux, напрямую обращаясь к физической памяти;поэтому очень важно, чтобы при каждой загрузке я получал одни и те же адреса.

Наверное стоит уточнить, что это для ядра 2.6.12 и новее.

РЕДАКТИРОВАТЬ3:Вот мой код: сначала для модуля ядра, затем для приложения пользовательского пространства.

Чтобы использовать его, я загружаюсь с 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;
}
Это было полезно?

Решение

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

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

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

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

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

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

virt_to_phys(pt) >> PAGE_SHIFT

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

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

Другие советы

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top