2012-05-25 8 views
11

Powiedzmy, że bufor jest przydzielany za pomocą schematu opartego na stronie. Jednym ze sposobów na wdrożenie mmap byłoby użycie remap_pfn_range, ale LDD3 mówi, że to nie działa w przypadku pamięci konwencjonalnej. Wygląda na to, że możemy obejść to poprzez zaznaczenie stron zarezerwowanych przy pomocy SetPageReserved, aby zablokować je w pamięci. Ale czy nie cała pamięć jądra już się nie wymienia, to znaczy już jest zarezerwowana? Dlaczego trzeba ustawić jawnie zarezerwowany bit?Jak zmapować bufor jądra Linux do przestrzeni użytkownika?

Czy ma to coś wspólnego ze stronami przydzielonymi z HIGH_MEM?

+0

Nie wiem, czy to pomoże, ale o ile mi wiadomo, [Perf] (http : //lxr.free-electrons.com/source/tools/perf/design.txt) podsystem w jądrze zawiera zestaw stron z pamięci jądra (pierścień w rzeczywistości bufor), które mogą być mmap'ed przez aplikacje przestrzeni użytkownika. Jego implementacja może dać pewne wskazówki dotyczące twojego pytania, może warto spojrzeć na jego kod źródłowy. – Eugene

Odpowiedz

16

Najprostszym sposobem odwzorowania zbioru stron z jądra w metodzie mmap jest użycie procedury obsługi błędów w celu odwzorowania stron. Zasadniczo można skończyć z czymś takim:

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, 
}; 

(jeżeli inne operacje na plikach są niezależnie od Twoich potrzeb Module). Również w przypadku my_mmap wykonujesz jakiekolwiek sprawdzanie zakresu itp., Aby zweryfikować parametry mmap.

Następnie vm_ops wyglądać następująco:

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 
} 

gdzie po prostu trzeba dowiedzieć się o danym VMA/przekazany do funkcji błędu, która strona do mapy w przestrzeni użytkownika VMF. To zależy od tego, jak działa twój moduł. Na przykład, jeśli nie

my_buf = vmalloc_user(MY_BUF_SIZE); 

wówczas strona użyć byłoby coś

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

Ale można łatwo utworzyć tablicę i przeznaczyć stronę dla każdego wpisu, użyj kmalloc, cokolwiek.

[tylko zauważył, że my_fault jest nieco zabawna nazwa funkcji]

+0

Dziękuję. Jest to bardzo pomocne. Czy nie musimy jednak wywoływać vm_insert_page w procedurze obsługi błędów? Kto cofnie stronę get_page, aby umożliwić później zwolnienie strony? Przypuszczam, że gdy przestrzeń użytkownika zrobi munmap, możemy uzyskać kod wykonany z vma_close, w którym możemy umieścić stronę dla wszystkich stron, które są uszkodzone. Czy to właściwe podejście? – ravi

+2

Nie, nie musisz wykonywać vm_insert_page, jeśli ustawisz vmf-> page.Jeśli robisz lepsze rzeczy na temat odwzorowywania pamięci urządzenia bez strony, możesz potrzebować vm_insert_pfn(), ale naprawdę pewnie nie chcesz się tym martwić. Funkcja put_page() jest obsługiwana przez rdzeń kodu VM, gdy mapowanie jest zerwane. Naprawdę, dla prostego sterownika, który mapuje pamięć jądra do przestrzeni użytkownika, pokazałem ci wszystko, czego potrzebujesz. – Roland

+0

Witam. Co byłoby ciałem metody my_fault(), gdyby nie można było vmalloc() - zjadł bufora my_buf? (ponieważ jest zbyt duży). Wyjaśnij alokację strona po stronie, na żądanie. – user1284631

0

Choć strony są zastrzeżone przez sterownik jądra, to ma być dostępna za pośrednictwem przestrzeni użytkownika. W rezultacie PTE (wpisy w tablicy stron) nie wiedzą, czy pfn należy do przestrzeni użytkownika lub przestrzeni jądra (mimo że są one przydzielane przez sterownik jądra).

Dlatego są oznaczone jako SetPageReserved.

2

Minimal runnable przykładem i testy 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; 
} 
Powiązane problemy