linux用户态和kernel之间共享内存 --- remap_pfn_range + mmap的实现方式

来源:互联网 发布:照相机内存卡数据恢复 编辑:程序博客网 时间:2024/06/08 05:06
概述:内核的驱动程序使用remap_pfn_range()结合设备驱动文件的mmap操作来将内存共享至用户态

-----------以下是内核态代码------
struct file_operations Fops = {
    .read = char_read,
    .write = char_write,
    .open = char_open,
    .release = char_release,
    .mmap = mem_mmap,
};


unsigned long gsize = 4096 * 10;
unsigned char *mem_msg_buf = NULL;
int memDev_init(){
.....
    mem_msg_buf = malloc_reserved_mem(gsize);
.....
}
module_init(memDev_init);

static unsigned char * malloc_reserved_mem(unsigned int size){
    unsigned char *p = kmalloc(size, GFP_KERNEL);
    unsigned char *tmp = p;
    unsigned int i, n;
    if (NULL == p){
        printk("Error : malloc_reserved_mem kmalloc failed!\n");
        return NULL;
    }
    n = size / 4096 + 1;
    if (0 == size % 4096 ){
        --n;
    }
    for (i = 0; i < n; ++i){
        SetPageReserved(virt_to_page(tmp));
        tmp += 4096;
    }
    return p;
}

int mem_mmap(struct file *filp, struct vm_area_struct *vma){
    printk("in mem_mmap\n");
    unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
    unsigned long  physics = ((unsigned long )mem_msg_buf)-PAGE_OFFSET;
    unsigned long mypfn = physics >> PAGE_SHIFT;
    unsigned long vmsize = vma->vm_end-vma->vm_start;
    unsigned long psize = gsize - offset;
    if(vmsize > psize)
        return -ENXIO;
    if(remap_pfn_range(vma,vma->vm_start,mypfn,vmsize,vma->vm_page_prot))
        return -EAGAIN;
    return 0;
}

-------------下面让我们来看一下用户态是怎么实现的吧-------------
用户态所要做的事情非常简单,
首先open驱动设备文件,
然后对该文件进行mmap操作就可以了,
进行mmap操作的时候,如果第一个参数是NULL,则有libc自己分配一块虚拟内存区域,这一块虚拟内存区域将对应于上面的vma
这里还需特别注意的三点就是这里的size的设置,这里的size的设置将决定上面所述vma中start 和 end之间的差,所以一定要和内核中物理内存区域大小对应起来。
另外这里PROT的设置,PROT的设置对应上面vma中的vm_page_prot,如果需要读写,则要设置为PROT_READ|PROT_WRITE
第三点就是MAP_SHARED的设置,如果设置成MAP_SHARED,用户态的虚拟空间就确实映射到了内核态中开辟的空间的物理地址上,就是实实在在的一块内存了。
当然这里也可以设置为MAP_PRIVATE,如果这么设置,那么在mmap的时候,是从内核态开辟的空间的物理地址上做一次copy,copy到用户态的虚拟地址上,所以这样事实上还是两块内存。

void *g_pBuffer = NULL;
int g_fd = -1;

void * getMemMsgBuf(){
    unsigned char * ret;
    if (NULL != g_pBuffer){
        return g_pBuffer;
    }

    if (g_fd < 0){
        g_fd = open("/dev/memMsgDev", O_RDWR);
        if (0 > g_fd){
            printf("Error : getMemMsgBuf() open /dev/memMsgDev failed , errno = %d \n", errno);
            return NULL;
        }
    }

    g_pBuffer = mmap(NULL, 4096 * 10,PROT_READ|PROT_WRITE,MAP_SHARED, g_fd,0 );
    if( g_pBuffer == MAP_FAILED ) {
        printf("Error : getMemMsgBuf() fail mmap!\n");
        return NULL;
    }

    return g_pBuffer;

}





做了一个外挂lcd模块的驱动,在内核区用kmalloc申请了(1 * PAGE_SIZE)的物理内存作为lcd数据帧缓冲,通过remap_pfn_range建立和用户空间的页表映射,在应用程序中使用mmap系统调用返回映射的用户虚拟内存的地址*ret;在用户空间向ret中写入要显示的图片,通过ioctl系统调用命令通知内核刷显lcd模块;问题:第一次用户更新ret后,内核空间对应的虚拟内存映射区可已得到实时更新,调用ioctl刷显图片正常;但再次更新用户空间ret缓冲后,在内核空间对应缓冲更新不实时,ioctl刷显后部分花屏,花屏区属于上次图片的显示数据;每次在用户空间更新ret显示缓冲后,需要等2秒以上的时间,再调用ioctl刷显才能正常刷屏;用户空间和内核空间的虚拟内存缓冲映射后对应的是同一块物理内存,问什么更新效率这么低呢??求高人帮忙!!!

部分用户应用代码:

int main()
{
int fd = 0;

unsigned long ch=0;
fd = open("/dev/lcd160dev0",O_RDWR|O_SYNC);
if(fd<0)
{
perror("fail open");
return -1;
}

ret = mmap(NULL, lcd_buff_size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_NORESERVE, fd, 0 );
if( ret == MAP_FAILED )
{
perror("fail mmap");
return -1;
}
     ioctl(fd, IOCTL_LCD_CMD_ON, 0);

     memcpy( ret, DISPLOGO1, 3200 );
    
    ioctl(fd, IOCTL_LCD_CMD_UPDATE, 0);

部分驱动源码:
unsigned char *lcd_data_buff = NULL;

static void my_vm_open(struct vm_area_struct *vma)
{
printk("VMA open\n");
}

static void my_vm_close(struct vm_area_struct *vma)
{
printk("VMA close.\n");
}

struct vm_operations_struct vm_ops = 
{
.open = my_vm_open,
.close = my_vm_close,
};

static int lcd_ioctl( struct inode *inode, struct file *file,
                unsigned int cmd, unsigned long arg)
{
unsigned int i=0;
switch (cmd)
{
case IOCTL_LCD_CMD_ON:
           lcd_initialize();
           LCD_BL_ON();
           printk(KERN_INFO "kernel lcd init success! \n");
           break;

case IOCTL_LCD_CMD_OFF:
           break;

case IOCTL_LCD_CMD_RESTOR:
           break;

case IOCTL_LCD_CMD_UPDATE:
               write_screen_data();
       printk(KERN_INFO "kernel lcd UPDATE success!! \n");
           break;
default:
  printk("Ioctl mode not support\n");
}
return 0;
}

static int my_mmap(struct file *flip,struct vm_area_struct *vma)
{

unsigned long physics,mypfn,vmsize; 
printk("in mmap\n");
  physics = virt_to_phys((void *)lcd_data_buff);
  mypfn = physics >> PAGE_SHIFT;
  vmsize = vma->vm_end-vma->vm_start;

if(vmsize > lcd_buff_size)
return -ENXIO;
if(remap_pfn_range(vma,vma->vm_start,mypfn,vmsize,vma->vm_page_prot))
return -EAGAIN;
vma->vm_ops = &vm_ops;
my_vm_open(vma);
return 0;
}
static int my_open(struct inode *inode ,struct file *flip)
{
printk("open file sucess\n");
return 0;
}

static int my_release(struct inode *inode ,struct file *flip)
{
printk("release file sucess\n");
return 0;
}
struct file_operations fops={
.owner = THIS_MODULE,
.open = my_open,
.release = my_release,
.ioctl= lcd_ioctl,
.mmap = my_mmap,
};



经进一步测试,每次通过系统调用进入内核态对映射区进行读操作后(即使是一个字节的操作),待返回用户应用程序后刷新映射区,再次系统调用进入内核态操作映射区就会发现数据更新不同步(大约需等2秒后正常更新);若进入内核态不对映射区进行操作,则数据更新正常,用户内核数据更新同步(测试方法:在用户态连续刷5张不同的图片数据,每次刷过后系统调用进入内核但不操作映射区返回,在第五次刷图片后系统调用将内核映射区的数据打印输出,则更新正常;若每次系统调用对内核映射进行操作,则最后一次输出数据乱);请求大侠指点一二!!!

0 0