2012-05-25 28 views
11

Giả sử bộ đệm được cấp phát bằng cách sử dụng lược đồ dựa trên trang. Một cách để thực hiện mmap sẽ là sử dụng remap_pfn_range nhưng LDD3 nói điều này không làm việc cho bộ nhớ thông thường. Có vẻ như chúng ta có thể giải quyết vấn đề này bằng cách đánh dấu (các) trang được dành riêng bằng cách sử dụng SetPageReserved để nó bị khóa trong bộ nhớ. Nhưng không phải tất cả bộ nhớ hạt nhân đã không thể hoán đổi được, tức là đã được bảo lưu chưa? Tại sao cần phải đặt bit được bảo lưu một cách rõ ràng?Làm thế nào để mmap một bộ đệm hạt nhân Linux để không gian người dùng?

Điều này có liên quan gì đến các trang được phân bổ từ HIGH_MEM không?

+0

Không chắc chắn nếu điều này giúp nhưng như xa như tôi biết, [Perf] (http : //lxr.free-electrons.com/source/tools/perf/design.txt) Hệ thống con trong hạt nhân cung cấp một tập hợp các trang từ bộ nhớ hạt nhân (một vòng bộ đệm, thực sự) có thể được các ứng dụng không gian người dùng tạo ra. Việc thực hiện của nó có thể có thể đưa ra một số gợi ý liên quan đến câu hỏi của bạn, có thể là đáng để xem xét mã nguồn của nó. – Eugene

Trả lời

16

Cách đơn giản nhất để ánh xạ một tập hợp các trang từ hạt nhân trong phương thức mmap của bạn là sử dụng trình xử lý lỗi để ánh xạ các trang. Về cơ bản bạn kết thúc bằng một cái gì đó như:

static int my_mmap(struct file *filp, struct vm_area_struct *vma) 
{ 
    vma->vm_ops = &my_vm_ops; 
    return 0; 
} 

static const struct file_operations my_fops = { 
    .owner = THIS_MODULE, 
    .open = nonseekable_open, 
    .mmap = my_mmap, 
    .llseek = no_llseek, 
}; 

(nơi các thao tác tệp khác là bất kỳ nhu cầu mô-đun của bạn). Cũng trong số my_mmap bạn thực hiện bất kỳ việc kiểm tra phạm vi nào là cần thiết để xác thực tham số mmap.

Sau đó giao diện vm_ops như:

static int my_fault(struct vm_area_struct *vma, struct vm_fault *vmf) 
{ 
    vmf->page = my_page_at_index(vmf->pgoff); 
    get_page(vmf->page); 

    return 0; 
} 

static const struct vm_operations_struct my_vm_ops = { 
    .fault  = my_fault 
} 

nơi bạn chỉ cần phải tìm ra cho một vma/VMF trao truyền cho chức năng là lỗi của bạn mà trang để ánh xạ vào không gian người dùng. Điều này phụ thuộc vào cách thức hoạt động của mô-đun của bạn. Ví dụ, nếu bạn đã làm

my_buf = vmalloc_user(MY_BUF_SIZE); 

sau đó trang web mà bạn sử dụng sẽ là một cái gì đó giống như

vmalloc_to_page(my_buf + (vmf->pgoff << PAGE_SHIFT)); 

Nhưng bạn có thể dễ dàng tạo ra một mảng và phân bổ một trang cho mỗi mục, sử dụng kmalloc, bất cứ điều gì.

[chỉ nhận thấy rằng my_fault là một cái tên hơi thú vị cho một chức năng]

+0

Cảm ơn bạn. Điều này khá hữu ích. Tuy nhiên, chúng ta không cần gọi vm_insert_page trong trình xử lý lỗi? Ngoài ra, ai sẽ hoàn tác get_page để cho phép trang được giải phóng sau này? Tôi giả sử khi không gian người dùng thực hiện munmap, chúng ta có thể lấy một số mã được thực thi từ vma_close, trong đó chúng ta có thể put_page cho tất cả các trang bị lỗi. Đây có phải là cách tiếp cận đúng? – ravi

+2

Không, bạn không cần phải làm vm_insert_page nếu bạn đặt vmf-> trang.Nếu bạn đang làm những thứ quan trọng xung quanh việc lập bản đồ bộ nhớ thiết bị không được hỗ trợ trang, thì bạn có thể cần vm_insert_pfn() nhưng thực sự bạn có thể không muốn lo lắng về điều đó. Put_page() được xử lý bởi mã vm lõi khi ánh xạ bị rách. Thực sự, đối với một trình điều khiển đơn giản ánh xạ bộ nhớ hạt nhân vào không gian người dùng, tôi đã cho bạn thấy khá nhiều thứ bạn cần. – Roland

+0

Xin chào. Điều gì sẽ là cơ thể của phương pháp my_fault() nếu nó sẽ không thể vmalloc() - ăn bộ đệm my_buf? (vì quá lớn). Imean phân bổ theo từng trang, theo yêu cầu. – user1284631

0

Mặc dù các trang được giữ lại qua một trình điều khiển hạt nhân, nó có nghĩa là để được truy cập thông qua không gian sử dụng. Kết quả là, các PTE (mục bảng trang) không biết nếu pfn thuộc về không gian người dùng hoặc không gian hạt nhân (mặc dù chúng được phân bổ thông qua trình điều khiển nhân).

Đây là lý do tại sao chúng được đánh dấu bằng SetPageReserved.

2

Minimal dụ Runnable và thử nghiệm Userland

Kernel module:

#include <asm/uaccess.h> /* copy_from_user */ 
#include <linux/debugfs.h> 
#include <linux/fs.h> 
#include <linux/init.h> 
#include <linux/kernel.h> /* min */ 
#include <linux/mm.h> 
#include <linux/module.h> 
#include <linux/proc_fs.h> 
#include <linux/slab.h> 

static const char *filename = "lkmc_mmap"; 

enum { BUFFER_SIZE = 4 }; 

struct mmap_info { 
    char *data; 
}; 

/* After unmap. */ 
static void vm_close(struct vm_area_struct *vma) 
{ 
    pr_info("vm_close\n"); 
} 

/* First page access. */ 
static int vm_fault(struct vm_area_struct *vma, struct vm_fault *vmf) 
{ 
    struct page *page; 
    struct mmap_info *info; 

    pr_info("vm_fault\n"); 
    info = (struct mmap_info *)vma->vm_private_data; 
    if (info->data) { 
     page = virt_to_page(info->data); 
     get_page(page); 
     vmf->page = page; 
    } 
    return 0; 
} 

/* Aftr mmap. TODO vs mmap, when can this happen at a different time than mmap? */ 
static void vm_open(struct vm_area_struct *vma) 
{ 
    pr_info("vm_open\n"); 
} 

static struct vm_operations_struct vm_ops = 
{ 
    .close = vm_close, 
    .fault = vm_fault, 
    .open = vm_open, 
}; 

static int mmap(struct file *filp, struct vm_area_struct *vma) 
{ 
    pr_info("mmap\n"); 
    vma->vm_ops = &vm_ops; 
    vma->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP; 
    vma->vm_private_data = filp->private_data; 
    vm_open(vma); 
    return 0; 
} 

static int open(struct inode *inode, struct file *filp) 
{ 
    struct mmap_info *info; 

    pr_info("open\n"); 
    info = kmalloc(sizeof(struct mmap_info), GFP_KERNEL); 
    pr_info("virt_to_phys = 0x%llx\n", (unsigned long long)virt_to_phys((void *)info)); 
    info->data = (char *)get_zeroed_page(GFP_KERNEL); 
    memcpy(info->data, "asdf", BUFFER_SIZE); 
    filp->private_data = info; 
    return 0; 
} 

static ssize_t read(struct file *filp, char __user *buf, size_t len, loff_t *off) 
{ 
    struct mmap_info *info; 
    int ret; 

    pr_info("read\n"); 
    info = filp->private_data; 
    ret = min(len, (size_t)BUFFER_SIZE); 
    if (copy_to_user(buf, info->data, ret)) { 
     ret = -EFAULT; 
    } 
    return ret; 
} 

static ssize_t write(struct file *filp, const char __user *buf, size_t len, loff_t *off) 
{ 
    struct mmap_info *info; 

    pr_info("write\n"); 
    info = filp->private_data; 
    if (copy_from_user(info->data, buf, min(len, (size_t)BUFFER_SIZE))) { 
     return -EFAULT; 
    } else { 
     return len; 
    } 
} 

static int release(struct inode *inode, struct file *filp) 
{ 
    struct mmap_info *info; 

    pr_info("release\n"); 
    info = filp->private_data; 
    free_page((unsigned long)info->data); 
    kfree(info); 
    filp->private_data = NULL; 
    return 0; 
} 

static const struct file_operations fops = { 
    .mmap = mmap, 
    .open = open, 
    .release = release, 
    .read = read, 
    .write = write, 
}; 

static int myinit(void) 
{ 
    proc_create(filename, 0, NULL, &fops); 
    return 0; 
} 

static void myexit(void) 
{ 
    remove_proc_entry(filename, NULL); 
} 

module_init(myinit) 
module_exit(myexit) 
MODULE_LICENSE("GPL"); 

Userland test:

#define _XOPEN_SOURCE 700 
#include <assert.h> 
#include <fcntl.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <stdint.h> /* uintmax_t */ 
#include <string.h> 
#include <sys/mman.h> 
#include <unistd.h> /* sysconf */ 

#include "common.h" /* virt_to_phys_user */ 

enum { BUFFER_SIZE = 4 }; 

int main(int argc, char **argv) 
{ 
    int fd; 
    long page_size; 
    char *address1, *address2; 
    char buf[BUFFER_SIZE]; 
    uintptr_t paddr; 

    if (argc < 2) { 
     printf("Usage: %s <mmap_file>\n", argv[0]); 
     return EXIT_FAILURE; 
    } 
    page_size = sysconf(_SC_PAGE_SIZE); 
    printf("open pathname = %s\n", argv[1]); 
    fd = open(argv[1], O_RDWR | O_SYNC); 
    if (fd < 0) { 
     perror("open"); 
     assert(0); 
    } 
    printf("fd = %d\n", fd); 

    /* mmap twice for double fun. */ 
    puts("mmap 1"); 
    address1 = mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 
    if (address1 == MAP_FAILED) { 
     perror("mmap"); 
     assert(0); 
    } 
    puts("mmap 2"); 
    address2 = mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 
    if (address2 == MAP_FAILED) { 
     perror("mmap"); 
     return EXIT_FAILURE; 
    } 
    assert(address1 != address2); 

    /* Read and modify memory. */ 
    puts("access 1"); 
    assert(!strcmp(address1, "asdf")); 
    /* vm_fault */ 
    puts("access 2"); 
    assert(!strcmp(address2, "asdf")); 
    /* vm_fault */ 
    strcpy(address1, "qwer"); 
    /* Also modified. So both virtual addresses point to the same physical address. */ 
    assert(!strcmp(address2, "qwer")); 

    /* Check that the physical addresses are the same. 
    * They are, but TODO why virt_to_phys on kernel gives a different value? */ 
    assert(!virt_to_phys_user(&paddr, getpid(), (uintptr_t)address1)); 
    printf("paddr1 = 0x%jx\n", (uintmax_t)paddr); 
    assert(!virt_to_phys_user(&paddr, getpid(), (uintptr_t)address2)); 
    printf("paddr2 = 0x%jx\n", (uintmax_t)paddr); 

    /* Check that modifications made from userland are also visible from the kernel. */ 
    read(fd, buf, BUFFER_SIZE); 
    assert(!memcmp(buf, "qwer", BUFFER_SIZE)); 

    /* Modify the data from the kernel, and check that the change is visible from userland. */ 
    write(fd, "zxcv", 4); 
    assert(!strcmp(address1, "zxcv")); 
    assert(!strcmp(address2, "zxcv")); 

    /* Cleanup. */ 
    puts("munmap 1"); 
    if (munmap(address1, page_size)) { 
     perror("munmap"); 
     assert(0); 
    } 
    puts("munmap 2"); 
    if (munmap(address2, page_size)) { 
     perror("munmap"); 
     assert(0); 
    } 
    puts("close"); 
    close(fd); 
    return EXIT_SUCCESS; 
} 
Các vấn đề liên quan