2009-03-15 57 views
41

Tôi đang cố gắng truy cập bộ nhớ vật lý trực tiếp cho một dự án Linux được nhúng, nhưng tôi không chắc chắn làm cách nào để có thể chỉ định bộ nhớ tốt nhất cho việc sử dụng của mình.Truy cập bộ nhớ trực tiếp trong Linux

Nếu tôi khởi động thiết bị của mình thường xuyên và truy cập/dev/mem, tôi có thể dễ dàng đọc và ghi vào bất kỳ nơi nào tôi muốn. Tuy nhiên, trong này, tôi đang truy cập vào bộ nhớ có thể dễ dàng được phân bổ cho bất kỳ quá trình nào; mà tôi không muốn làm

Mã của tôi cho/dev/mem là (tất cả các kiểm tra lỗi, vv loại bỏ):

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); 

Và công trình này. Tuy nhiên, tôi muốn sử dụng bộ nhớ mà không ai khác sẽ chạm vào. Tôi đã thử giới hạn số lượng bộ nhớ mà hạt nhân nhìn thấy bằng cách khởi động với mem = XXXm, và sau đó thiết lập BASE_ADDRESS thành một cái gì đó ở trên (nhưng bên dưới bộ nhớ vật lý), nhưng dường như không thể truy cập cùng một bộ nhớ một cách nhất quán.

Dựa trên những gì tôi đã xem trực tuyến, tôi nghi ngờ có thể cần một mô-đun hạt nhân (OK) sử dụng ioremap() hoặc remap_pfn_range() (hoặc cả hai ???), nhưng tôi hoàn toàn không có ý tưởng làm sao; có ai giúp được không?

EDIT: Điều tôi muốn là cách luôn truy cập cùng một bộ nhớ vật lý (giả sử 1,5MB) và đặt bộ nhớ sang một bên sao cho hạt nhân sẽ không cấp phát cho bất kỳ quá trình nào khác.

Tôi đang cố gắng để tái tạo một hệ thống chúng tôi đã có trong hệ điều hành khác (không có quản lý bộ nhớ) nhờ đó mà tôi có thể bố trí một không gian trong bộ nhớ thông qua mối liên kết và truy cập nó bằng cách sử cái gì đó như

*(unsigned char *)0x12345678 

EDIT2: Tôi đoán tôi nên cung cấp thêm một số chi tiết. Không gian bộ nhớ này sẽ được sử dụng cho bộ đệm RAM cho giải pháp ghi nhật ký hiệu suất cao cho một ứng dụng nhúng. Trong các hệ thống chúng tôi có, không có gì xóa hoặc xáo trộn bộ nhớ vật lý trong khi khởi động lại mềm. Vì vậy, nếu tôi viết một chút vào một địa chỉ vật lý X, và khởi động lại hệ thống, cùng một bit sẽ vẫn được thiết lập sau khi khởi động lại. Điều này đã được thử nghiệm trên cùng một phần cứng chạy VxWorks (logic này cũng hoạt động tốt trong Nucleus RTOS và OS20 trên các nền tảng khác nhau, FWIW). Ý tưởng của tôi là thử cùng một thứ trong Linux bằng cách giải quyết trực tiếp bộ nhớ vật lý; do đó, điều quan trọng là tôi nhận được cùng một địa chỉ mỗi lần khởi động.

Tôi có lẽ nên làm rõ rằng đây là hạt nhân 2.6.12 và mới hơn.

EDIT3: Đây là mã của tôi, trước tiên cho mô-đun hạt nhân, sau đó cho ứng dụng không gian người dùng.

Để sử dụng nó, tôi khởi động với mem = 95m, sau đó insmod foo-module.ko, sau đó mknod mknod/dev/foo c 32 0, sau đó chạy foo-user, nơi nó chết. Chạy theo gdb cho thấy rằng nó chết tại nhiệm vụ, mặc dù trong gdb, tôi có thể không dereference địa chỉ tôi nhận được từ mmap (mặc dù printf thể)

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

Để làm rõ, bạn muốn (trong mô-đun) trả về một không gian địa chỉ cho không gian người dùng được mua qua vmalloc(), không phải là kmalloc(), đúng không? Bạn cần bao nhiêu bộ nhớ? –

+0

Điều này có thể được thực hiện dễ dàng nhất với kmalloc(), những gì bạn muốn làm là thiết lập 1,5 MB không gian hạt nhân ngoài và đưa nó vào không gian người dùng. Nếu đó là những gì bạn muốn làm, tôi sẽ làm mới bản thân mình trên một vài hạt nhân bên trong và cố gắng trả lời. –

+0

Lưu ý, làm điều này với vmalloc() có thể là một nhiệm vụ cực kỳ đáng ghét. Số tiền bạn thực sự cần để ánh xạ ảnh hưởng đến câu trả lời, vì vậy bạn chắc chắn 1,5 MB hoặc ít hơn? –

Trả lời

13

Tôi nghĩ bạn có thể tìm thấy nhiều tài liệu về phần kmalloc + mmap. Tuy nhiên, tôi không chắc chắn rằng bạn có thể kmalloc bộ nhớ rất nhiều trong một cách tiếp giáp, và có nó luôn luôn ở cùng một vị trí. Chắc chắn, nếu mọi thứ luôn giống nhau, thì bạn có thể nhận được một địa chỉ liên tục. Tuy nhiên, mỗi khi bạn thay đổi mã hạt nhân, bạn sẽ nhận được một địa chỉ khác, vì vậy tôi sẽ không đi với giải pháp kmalloc.

Tôi nghĩ bạn nên đặt trước một số bộ nhớ lúc khởi động, tức là đặt trước một số bộ nhớ vật lý sao cho hạt nhân không bị chạm vào. Sau đó, bạn có thể ioremap bộ nhớ này sẽ cung cấp cho bạn một địa chỉ ảo hạt nhân, và sau đó bạn có thể mmap nó và viết một trình điều khiển thiết bị tốt đẹp.

Điều này đưa chúng tôi trở lại linux device drivers ở định dạng PDF. Hãy xem chương 15, nó mô tả kỹ thuật này trên trang 443

Chỉnh sửa: ioremap và mmap. Tôi nghĩ rằng điều này có thể dễ dàng hơn để gỡ lỗi khi thực hiện hai bước: trước hết hãy lấy ioremap bên phải và kiểm tra nó bằng thao tác thiết bị ký tự, nghĩa là đọc/ghi. Một khi bạn biết bạn có thể an toàn có quyền truy cập vào toàn bộ bộ nhớ ioremapped bằng cách sử dụng đọc/ghi, sau đó bạn cố gắng mmap toàn bộ dải ioremapped.

Và nếu bạn gặp rắc rối có thể gửi một câu hỏi về mmaping

Edit: remap_pfn_range ioremap trả về một virtual_adress, mà bạn phải chuyển sang một PFN cho remap_pfn_ranges. Bây giờ, tôi không hiểu chính xác những gì một PFN (Khung trang Number), nhưng tôi nghĩ rằng bạn có thể nhận được một cuộc gọi

virt_to_phys(pt) >> PAGE_SHIFT 

Điều này có lẽ không phải là Way Right (tm) để làm điều đó, nhưng bạn nên thử nó

Bạn cũng nên kiểm tra xem FOO_MEM_OFFSET có phải là địa chỉ thực của khối RAM của bạn hay không. Tức là trước khi bất cứ điều gì xảy ra với mmu, bộ nhớ của bạn có sẵn ở 0 trong bản đồ bộ nhớ của bộ xử lý của bạn.

+0

Khi bạn nói "Tôi nghĩ bạn nên đặt trước một số bộ nhớ lúc khởi động, tức là đặt trước một số bộ nhớ vật lý sao cho hạt nhân không bị chạm vào". Bạn có nghĩa là khởi động với mem = XXXm, trong đó XXX nhỏ hơn địa chỉ thực tế? Đó là những gì tôi đã suy nghĩ ban đầu. – Mikeage

+0

[cont] Tôi thấy một số mã có sử dụng ioremap(); Tôi sẽ thử cái này Vì vậy, để xác nhận: khởi động với mem = XXX, ioremap (XXX + 1), và sau đó cách tốt nhất để dịch địa chỉ này sang địa chỉ người dùng là gì? 1 – Mikeage

+0

Hrmmm. Tôi đã thử điều này, và thực hiện các mmap như sau (tất cả các lỗi kiểm tra loại bỏ): static int foo_mmap (struct tập tin * 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

Xin lỗi để trả lời nhưng không hoàn toàn trả lời, tôi nhận thấy rằng bạn đã chỉnh sửa câu hỏi. Xin lưu ý rằng SO không thông báo cho chúng tôi khi bạn chỉnh sửa câu hỏi. Tôi đang đưa ra một câu trả lời chung ở đây, khi bạn cập nhật câu hỏi xin vui lòng để lại một bình luận, sau đó tôi sẽ chỉnh sửa câu trả lời của tôi.

Có, bạn sẽ cần viết mô-đun. Những gì nó đi xuống là việc sử dụng các kmalloc() (phân bổ một khu vực trong không gian hạt nhân) hoặc vmalloc() (phân bổ một khu vực trong không gian người dùng).

Việc phơi bày trước dễ dàng, việc phơi bày thứ hai có thể là một cơn đau ở phía sau với loại giao diện bạn mô tả khi cần. Bạn lưu ý 1,5 MB là một ước tính sơ bộ về số tiền bạn thực sự cần phải dự trữ, đó là sắt mạ? I.e là bạn thoải mái lấy điều đó từ không gian hạt nhân? Bạn có thể đối phó đầy đủ với ENOMEM hoặc EIO từ không gian người dùng (hoặc thậm chí là đĩa ngủ) không? IOW, những gì đang xảy ra trong khu vực này?

Ngoài ra, đồng thời có phải là vấn đề với điều này không? Nếu vậy, bạn sẽ sử dụng một futex? Nếu câu trả lời cho một trong hai là 'có' (đặc biệt là sau này), có khả năng là bạn sẽ phải cắn viên đạn và đi với vmalloc() (hoặc nguy cơ bị thối hạt nhân từ bên trong). Ngoài ra, nếu bạn thậm chí còn THINKING về giao diện ioctl() với thiết bị char (đặc biệt là đối với một số ý tưởng khóa-hoc), bạn thực sự muốn đi với vmalloc().

Ngoài ra, bạn đã đọc this chưa? Thêm vào đó, chúng tôi thậm chí không chạm vào những gì grsec/selinux sẽ nghĩ về điều này (nếu sử dụng).

+0

đây là một hệ thống nhúng; Tôi không lo lắng về selinux. Liên quan đến phần còn lại của Q của bạn, tôi đang thêm một số chi tiết hơn bây giờ. – Mikeage

0

Tôi đến nay không có chuyên gia về những vấn đề này, vì vậy đây sẽ là một câu hỏi cho bạn chứ không phải là câu trả lời. Có lý do gì khiến bạn không thể tạo một phân vùng đĩa ram nhỏ và chỉ sử dụng nó cho ứng dụng của bạn không? Điều đó sẽ không cung cấp cho bạn quyền truy cập được bảo đảm vào cùng một bộ nhớ? Tôi không chắc chắn sẽ có bất kỳ vấn đề hiệu suất I/O nào, hoặc phí bổ sung liên quan đến việc đó. Điều này cũng giả định rằng bạn có thể nói hạt nhân để phân vùng một dải địa chỉ cụ thể trong bộ nhớ, không chắc chắn nếu điều đó là có thể.

Tôi xin lỗi vì câu hỏi mới, nhưng tôi thấy câu hỏi của bạn thú vị và tò mò nếu đĩa ram có thể được sử dụng theo cách như vậy.

+0

Đồng thời có thể là một vấn đề với điều này, đặc biệt là khi sử dụng DM không tôn trọng các rào cản viết. Câu hỏi (mặc dù chỉnh sửa) thực sự mơ hồ, làm thế nào họ sẽ thực sự sử dụng loại khu vực với loại giao diện đó là thiếu. –

+0

Ngoài ra, (tham khảo bình luận đầu tiên của tôi) ghi bộ nhớ cache có thể là một vấn đề, đặc biệt là khi đặt hàng. –

+0

Ramdisk sẽ không hoạt động; xem chỉnh sửa mới nhất của tôi – Mikeage

2

/dev/mem được chấp nhận cho các lần đăng nhập và pokes đăng ký đơn giản, nhưng một khi bạn vượt qua các ngắt và lãnh thổ DMA, bạn thực sự nên viết trình điều khiển chế độ hạt nhân. Những gì bạn đã làm cho các hệ điều hành không cần quản lý bộ nhớ trước đây của bạn đơn giản là không ghép tốt vào một hệ điều hành mục đích chung như Linux.

Bạn đã nghĩ về vấn đề phân bổ bộ đệm DMA. Bây giờ, suy nghĩ về "DMA thực hiện" ngắt từ thiết bị của bạn. Làm thế nào bạn sẽ cài đặt một thói quen dịch vụ gián đoạn?

Bên cạnh đó,/dev/mem thường bị khóa cho người dùng không phải root, do đó, nó không thực tế cho việc sử dụng chung. Chắc chắn, bạn có thể chmod nó, nhưng sau đó bạn đã mở một lỗ hổng bảo mật lớn trong hệ thống.

Nếu bạn đang cố gắng giữ cho mã cơ sở trình điều khiển tương tự giữa các hệ điều hành, bạn nên xem xét tái cấu trúc nó thành người dùng riêng biệt & các lớp chế độ hạt nhân với giao diện giống như IOCTL ở giữa. Nếu bạn viết phần chế độ người dùng như một thư viện chung của mã C, nó sẽ dễ dàng chuyển đổi giữa Linux và các hệ điều hành khác. Phần hệ điều hành cụ thể là mã chế độ lõi. (Chúng tôi sử dụng loại phương pháp này cho các tài xế của chúng tôi.)

Có vẻ như bạn đã kết luận rằng đã đến lúc viết trình điều khiển hạt nhân, vì vậy bạn đang đi đúng hướng. Lời khuyên duy nhất tôi có thể thêm là đọc những cuốn sách này.

Linux Device Drivers

Understanding the Linux Kernel

(Hãy ghi nhớ rằng những cuốn sách này là vào khoảng năm 2005, do đó thông tin là một chút ngày.)

0

Bạn đã xem thông số hạt nhân 'memmap' chưa? Trên i386 và X64_64, bạn có thể sử dụng tham số memmap để xác định hạt nhân sẽ xử lý các khối bộ nhớ rất cụ thể như thế nào (xem tài liệu Linux kernel parameter). Trong trường hợp của bạn, bạn muốn đánh dấu bộ nhớ là 'bảo lưu' để Linux không chạm vào nó. Sau đó, bạn có thể viết mã của bạn để sử dụng địa chỉ tuyệt đối và kích thước (woe được unto bạn nếu bạn bước ra ngoài không gian đó).

+0

Tôi thực sự đã sử dụng memmap [hoặc chỉ mem = XXm @ YY, mà hạt nhân của tôi hỗ trợ để đặt trước một khối ở giữa.] Vấn đề mở là cách truy cập trực tiếp vào bộ nhớ. – Mikeage

Các vấn đề liên quan