web-dev-qa-db-ja.com

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()(またはその両方)のいずれかを使用するカーネルモジュール(大丈夫です)が必要かもしれないと思いますが、どうすればいいかまったくわかりません。誰でも助けることができますか?

編集:私が欲しいのは、常に同じ物理メモリ(たとえば1.5MB相当)にアクセスし、カーネルが他のプロセスにメモリを割り当てないようにそのメモリを確保する方法です。

私は他のOSにあったシステムを(メモリ管理なしで)再現しようとしています。これにより、リンカを介してメモリ内のスペースを割り当て、次のようなものを使用してアクセスできます

*(unsigned char *)0x12345678

EDIT2:もう少し詳しく説明する必要があると思います。このメモリ空間は、RAM組み込みアプリケーションの高性能ロギングソリューションのバッファに使用されます。現在のシステムでは、ソフトリブート中に物理メモリをクリアまたはスクランブルするものはありません。 、物理アドレス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;
}
44
Mikeage

Kmalloc + mmapの部分に関する多くのドキュメントを見つけることができると思います。ただし、大量のメモリを連続してkmallocして、常に同じ場所に配置できるかどうかはわかりません。もちろん、すべてが常に同じであれば、一定のアドレスを取得できます。ただし、カーネルコードを変更するたびに異なるアドレスが取得されるため、kmallocソリューションを使用しません。

ブート時にメモリを予約する必要があると思います。つまり、カーネルが触れないように物理メモリを予約する必要があります。次に、このメモリをioremapしてカーネル仮想アドレスを取得し、それをmmapしてNiceデバイスドライバーを作成します。

Linuxデバイスドライバー in PDF format。に戻ります。第15章をご覧ください。このテクニックについては、443ページで説明しています。

編集:ioremapとmmap。これは、2つのステップで物事をデバッグする方が簡単だと思います。最初にioremapを正しく取得し、キャラクタデバイス操作(読み取り/書き込み)を使用してテストします。読み取り/書き込みを使用してioremappedメモリ全体に安全にアクセスできることがわかったら、ioremapped範囲全体をmmapしようとします。

困った場合は、mmapingに関する別の質問を投稿してください。

編集:remap_pfn_range ioremapは、remap_pfn_rangesのpfnに変換する必要があるvirtual_adressを返します。今、pfn(ページフレーム番号)が何であるかを正確に理解していませんが、1つの呼び出しを取得できると思います

virt_to_phys(pt) >> PAGE_SHIFT

これはおそらく正しい方法(tm)ではありませんが、試してみるべきです

また、FOO_MEM_OFFSETがRAMブロックの物理アドレスであることを確認する必要があります。つまり、mmuで何かが起こる前に、プロセッサのメモリマップでメモリが0で利用可能です。

16
shodanex

回答して申し訳ありませんが、まったく回答していません。すでに質問を編集していることに気付きました。 SOは質問を編集しても通知されません。ここで一般的な回答をします。質問を更新するときはコメントを残してください。その後、回答を編集します。

はい、モジュールを作成する必要があります。それは、kmalloc()(カーネル空間に領域を割り当てる)またはvmalloc()(ユーザー空間に領域を割り当てる)を使用することです。

前者を公開するのは簡単ですが、後者を公開することは、必要に応じて説明している種類のインターフェイスで後部の痛みになる可能性があります。 1.5 MBは実際に予約する必要がある量のおおよその見積もりであることに注意してください。つまり、カーネル空間からそれを快適に取っていますか?ユーザー空間からENOMEMまたはEIO(またはディスクスリープ)を適切に処理できますか? IOW、この地域には何が起こっているのですか?

また、並行性はこれに関する問題になりますか?もしそうなら、フューテックスを使用するつもりですか?いずれかの答えが「はい」(特に後者)の場合、弾丸を噛んでvmalloc()(または内部からカーネルが腐敗するリスク)を踏まなければならない可能性があります。また、charデバイスへのioctl()インターフェース(特にアドホックロックのアイデア)について考えている場合でも、vmalloc()を使用することをお勧めします。

また、 this ?さらに、grsec/selinuxがこれについてどう考えているかについても触れていません(使用している場合)。

14
Tim Post

/ dev/memは、単純なレジスタピークとポークには問題ありませんが、割り込みとDMA領域)を超えたら、カーネルモードドライバーを作成する必要があります。管理のないOSは、Linuxのような汎用OSにうまく適合しません。

DMAバッファ割り当ての問題について既に考えました。次に、デバイスからの "DMA done"割り込みについて考えます。どのように割り込みサービスルーチンをインストールしますか?

また、/ dev/memは通常、非rootユーザーに対してロックアウトされているため、一般的な使用にはあまり実用的ではありません。もちろん、それをchmodすることもできますが、システムに大きなセキュリティホールを開けました。

OS間でドライバのコードベースを同様に保ちたい場合は、IOCTLのようなインターフェイスを備えた別個のユーザーおよびカーネルモードレイヤーにリファクタリングすることを検討する必要があります。ユーザーモード部分をCコードの汎用ライブラリとして記述する場合、Linuxと他のOSの間で簡単に移植できます。 OS固有の部分は、カーネルモードコードです。 (この種のアプローチはドライバーに使用します。)

カーネルドライバーを作成する時が来たと既に結論付けているようで、正しい方向に進んでいます。私が追加できる唯一のアドバイスは、これらの本をカバーツーカバーで読むことです。

Linuxデバイスドライバー

Linuxカーネルについて

(これらの本は2005年頃のものであるため、情報は少し古くなっています。)

4
myron-semack

「memmap」カーネルパラメータを見ましたか? i386およびX64_64では、memmapパラメータを使用して、カーネルが非常に特定のメモリブロックを渡す方法を定義できます( Linux kernel parameter documentationを参照)。あなたのケースでは、Linuxがメモリにまったく触れないように、メモリを「予約済み」としてマークする必要があります。次に、その絶対アドレスとサイズを使用するコードを記述できます(そのスペースの外に出た場合は、あなたにとっては困ります)。

1
Craig Trader

私はこれらの問題に関する専門家ではないので、これは答えではなくあなたへの質問になります。小さなRAMディスクパーティションを作成して、アプリケーションにのみ使用できない理由はありますか?同じメモリチャンクへのアクセスが保証されていませんか? I/Oパフォーマンスの問題が発生するのか、それを行うことに伴う追加のオーバーヘッドが発生するのかはわかりません。また、これは、カーネルに特定のアドレス範囲をメモリ内でパーティション分割するように指示できることを前提としていますが、それが可能かどうかはわかりません。

Newbの質問にはおaび申し上げますが、あなたの質問は興味深いものであり、そのような方法でRAMディスクを使用できるかどうか興味があります。

1
pseudosaint