2012-05-25 17 views
11

Digamos que el buffer se asigna usando un esquema basado en página. Una forma de implementar mmap sería usar remap_pfn_range pero LDD3 dice que esto no funciona para la memoria convencional. Parece que podemos evitar esto marcando las páginas reservadas utilizando SetPageReserved para que se bloquee en la memoria. Pero, ¿no es toda la memoria del kernel ya no intercambiable, es decir, ya está reservada? ¿Por qué la necesidad de establecer el bit reservado explícitamente?Cómo mapear un buffer del kernel de Linux al espacio del usuario?

¿Tiene esto algo que ver con las páginas asignadas desde HIGH_MEM?

+0

No estoy seguro si esto ayuda, pero por lo que yo sé, [Perf] (http : //lxr.free-electrons.com/source/tools/perf/design.txt) subsistema en el núcleo proporciona un conjunto de páginas de la memoria del kernel (un anillo buffer, en realidad) que puede ser mapeado por aplicaciones de espacio de usuario. Su implementación podría dar algunos consejos sobre su pregunta, puede ser que valga la pena ver su código fuente. – Eugene

Respuesta

16

La forma más sencilla de asignar un conjunto de páginas desde el kernel en su método mmap es usar el controlador de fallas para mapear las páginas. Básicamente terminas con algo como:

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

(donde las demás operaciones de archivos son las que necesita tu módulo). También en my_mmap se hace la verificación de rango, etc. necesaria para validar los parámetros de mmap.

Entonces el vm_ops aspecto:

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 
} 

en el que sólo tiene que averiguar para un VMA/VMF dado pasado a la función falla que la página de mapa en el espacio de usuario. Esto depende exactamente de cómo funciona su módulo. Por ejemplo, si se hizo

my_buf = vmalloc_user(MY_BUF_SIZE); 

entonces la página se utiliza sería algo así como

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

Pero usted puede fácilmente crear una matriz y asignar una página para cada entrada, uso kmalloc, lo que sea.

[Acabo de notar que my_fault es un nombre ligeramente divertida para una función]

+0

Gracias. Esto es bastante útil. Sin embargo, ¿no necesitamos llamar a vm_insert_page en el controlador de fallas? Además, ¿quién deshará el get_page para permitir que la página se libere más tarde? Supongo que una vez que el espacio de usuario hace munmap, podemos obtener un código de vma_close en el que podríamos poner páginas para todas las páginas con fallas. ¿Es este el enfoque correcto? – ravi

+2

No, no necesita hacer vm_insert_page si establece vmf-> page.Si está haciendo más cosas al mapeo de la memoria del dispositivo sin respaldo de página, entonces es posible que necesite vm_insert_pfn(), pero realmente es probable que no quiera preocuparse por eso. Put_page() es manejado por el código core vm cuando se derriba la asignación. Realmente, para un controlador simple que mapea la memoria del kernel en el espacio de usuario, le mostré prácticamente todo lo que necesita. – Roland

+0

Hola. ¿Cuál sería el cuerpo del método my_fault() si fuera imposible vmalloc() - ate the my_buf buffer? (porque es demasiado grande) Me refiero a una asignación de página por página, según demanda. – user1284631

0

Aunque las páginas se reservan a través de un controlador del núcleo, que está destinado a ser accedido a través del espacio de usuario. Como resultado, las PTE (entradas de la tabla de páginas) no saben si la pfn pertenece al espacio de usuario o al espacio del kernel (aunque se asignan a través del controlador kernel).

Es por eso que están marcados con SetPageReserved.

2

ejemplo Mínimo ejecutable y Prueba de usuario

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; 
} 
Cuestiones relacionadas