45

組み込み 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.5MB相当)にアクセスし、そのメモリを脇に置いて、カーネルがそれを他のプロセスに割り当てないようにする方法です。

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

*(unsigned char *)0x12345678

EDIT2:もう少し詳細を提供する必要があると思います。このメモリ空間は、組み込みアプリケーションの高性能ロギング ソリューションの RAM バッファに使用されます。私たちが使用しているシステムでは、ソフト リブート中に物理メモリをクリアまたはスクランブルするものは何もありません。したがって、物理アドレス X にビットを書き込んでシステムを再起動すると、再起動後も同じビットが設定されたままになります。これは、VxWorks を実行しているまったく同じハードウェアでテストされています (このロジックは、異なるプラットフォーム (FWIW) 上の Nucleus RTOS および OS20 でもうまく機能します)。私のアイデアは、物理メモリを直接アドレス指定することで、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;
}
4

5 に答える 5

16

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

起動時にいくらかのメモリを予約するべきだと思います。つまり、カーネルが触れないように物理メモリを予約します。次に、このメモリを ioremap してカーネル仮想アドレスを取得し、mmap して適切なデバイス ドライバを作成します。

これにより、PDF 形式のLinux デバイス ドライバーに戻ります。第15章を見てください。443ページでこのテクニックについて説明しています。

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

また、問題が発生した場合は、mmaping に関する別の質問を投稿してください。

編集: remap_pfn_range ioremap は virtual_address を返します。これは、remap_pfn_ranges の pfn に変換する必要があります。さて、pfn (Page Frame Number) が何であるか正確にはわかりませんが、1 つの呼び出しを取得できると思います

virt_to_phys(pt) >> PAGE_SHIFT

これはおそらく正しい方法 (tm) ではありませんが、試してみてください。

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

于 2009-03-16T09:07:33.797 に答える
14

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

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

前者を公開するのは簡単ですが、後者を公開するのは、必要に応じて説明している種類のインターフェイスでは面倒な場合があります。1.5 MB は、実際に予約する必要がある量のおおよその見積もりであると指摘しましたが、それは鉄壁ですか? つまり、カーネル空間からそれを取得しても問題ありませんか? ユーザー空間からの ENOMEM または EIO (またはディスク スリープ) を適切に処理できますか? IOW、この地域に何が起こっているのですか?

また、並行性はこれで問題になりますか? もしそうなら、futexを使うつもりですか?いずれかに対する答えが「はい」の場合 (特に後者)、弾丸をかじって進む必要がある可能性がありますvmalloc()(または、内部からカーネルが腐敗するリスクを冒す必要があります)。また、ioctl()char デバイスへのインターフェースについて考えている場合 (特にアドホックなロックのアイデアの場合) は、本当にvmalloc().

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

于 2009-03-15T14:23:34.467 に答える
4

/dev/mem は、単純なレジスタのピークとポークには問題ありませんが、割り込みと DMA の領域にまたがると、カーネル モード ドライバーを作成する必要があります。以前のメモリ管理のない OS に対して行ったことは、Linux のような汎用 OS にはうまく移植できません。

DMA バッファ割り当ての問題についてはすでに考えました。ここで、デバイスからの「DMA 完了」割り込みについて考えてみましょう。割り込みサービス ルーチンをどのようにインストールしますか?

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

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

カーネルドライバーを書く時が来たとすでに結論付けているようですので、あなたは正しい道を進んでいます。私が追加できる唯一のアドバイスは、これらの本を最初から最後まで読むことです。

Linux デバイス ドライバー

Linux カーネルを理解する

(これらの本は 2005 年頃のものであるため、情報が少し古いことに注意してください。)

于 2009-03-15T19:27:52.927 に答える
1

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

于 2009-10-28T17:36:48.467 に答える
1

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

新規の質問で申し訳ありませんが、あなたの質問は興味深いものでした。RAM ディスクをそのような方法で使用できるかどうか興味があります。

于 2009-03-15T17:09:41.370 に答える