2012-05-25 1 views
11

Supponiamo che il buffer sia allocato utilizzando uno schema basato sulla pagina. Un modo per implementare mmap sarebbe utilizzare remap_pfn_range ma LDD3 dice che questo non funziona per la memoria convenzionale. Sembra che possiamo aggirare questo segnando la pagina (s) riservata usando SetPageReserved in modo che venga bloccato in memoria. Ma non tutta la memoria del kernel è già non sostituibile, cioè già riservata? Perché la necessità di impostare esplicitamente il bit riservato?Come mmap un buffer del kernel Linux nello spazio utente?

Ha qualcosa a che fare con le pagine allocate da HIGH_MEM?

+0

Non sono sicuro se questo aiuta, ma per quanto ne so, [Perf] (http : //lxr.free-electrons.com/source/tools/perf/design.txt) il sottosistema nel kernel fornisce un set di pagine dalla memoria del kernel (un anello buffer, in realtà) che può essere mmap'ed dalle applicazioni dello spazio utente. La sua implementazione potrebbe dare qualche suggerimento sulla tua domanda, potrebbe essere che valga la pena di guardare il suo codice sorgente. – Eugene

risposta

16

Il modo più semplice per mappare un insieme di pagine dal kernel nel metodo mmap è utilizzare il gestore degli errori per mappare le pagine. Fondamentalmente si finisce con qualcosa del tipo:

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

(dove le altre operazioni sui file sono quelle di cui il modulo ha bisogno). Anche in my_mmap si fa qualsiasi controllo di intervallo, ecc. È necessario per convalidare i parametri mmap.

Poi il vm_ops aspetto simile:

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 
} 

in cui hai solo bisogno di capire per un determinato VMA/VMF passato alla vostra funzione di guasto quale pagina per mappare in userspace. Questo dipende esattamente da come funziona il tuo modulo. Ad esempio, se avete fatto

my_buf = vmalloc_user(MY_BUF_SIZE); 

quindi la pagina si utilizza sarebbe qualcosa di simile

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

Ma si potrebbe facilmente creare un array e allocare una pagina per ogni voce, l'uso kmalloc, qualsiasi cosa.

[appena notato che my_fault è un nome un po 'divertente, per una funzione]

+0

Grazie. Questo è abbastanza utile. Non dobbiamo tuttavia chiamare vm_insert_page nel gestore degli errori? Inoltre, chi annullerà la get_page per consentire il rilascio della pagina in un secondo momento? Suppongo che una volta che user-space fa munmap, possiamo ottenere del codice esercitato da vma_close in cui potremmo mettere_pagina per tutte le pagine che hanno causato errori. È questo l'approccio giusto? – ravi

+2

No, non è necessario fare vm_insert_page se si imposta vmf-> page.Se stai facendo cose più appassionate sulla mappatura della memoria del dispositivo non basata su pagine, allora potresti aver bisogno di vm_insert_pfn() ma in realtà probabilmente non ti devi preoccupare di questo. Put_page() viene gestito dal codice vm di base quando il mapping viene rimosso. In realtà, per un semplice driver che mappa la memoria del kernel in userspace, ti ho mostrato praticamente tutto ciò di cui hai bisogno. – Roland

+0

Ciao. Quale sarebbe il corpo del metodo my_fault() se sarebbe impossibile vmalloc() - ha mangiato il buffer my_buf? (perché troppo grande). Assegnare un'allocazione pagina per pagina, su richiesta. – user1284631

0

Anche se le pagine sono riservate tramite un driver del kernel, si è pensato per essere accessibile tramite user space. Di conseguenza, le PTE (voci della tabella di pagina) non sanno se il pfn appartiene allo spazio utente o allo spazio del kernel (anche se sono allocate tramite il driver del kernel).

Questo è il motivo per cui sono contrassegnati con SetPageReserved.

2

Minimal esempio eseguibile e prova 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; 
}