嵌入式设计实验四:内存分配与内存映射


一、实验目的

掌握字符设备驱动程序中利用nopage进行内存映射的方法。
掌握利用get_free_pages进行连续物理地址空间申请的方法。

二、实验环境

ubuntu 12.04 内核3.2.14

三、实验内容及实验原理

写一个简单的驱动程序,要求:
在加载驱动程序时利用get_free_pages函数申请一片64KB的连续物理地址空间;
利用vma fault机制实现对申请到的64KB地址空间进行内存映射;
编写应用程序利用mmap进行内存映射,读写映射内存区域,通过打印输出观察具体每个页面实际进行内存映射的时机;
在卸载驱动程序时利用free_pages释放申请到的64KB空间

四、实验结果及其分析

1.编译模块(设备驱动程序)
(1)创建模块文件xxx.c
gedit rwbuf.c

// 模块
#include <linux/module.h>
// 内核
#include <linux/kernel.h>
// struct file_operations
#include <linux/fs.h>
// kmalloc和kfree
#include <linux/slab.h>
// 内存映射
#include <linux/mm.h>
#include <asm/page.h>
#include <linux/gfp.h>
#include <linux/vmalloc.h>
#include <linux/fcntl.h>
#include <linux/unistd.h>

#define DEVICE_CNT 1
#define DEVICE_NAME "rwbuf"

// 全局地址
static unsigned long addr;

void vma_open(struct vm_area_struct *vma)
{
    printk("[vma_open-success] virt = %lx, phys = %lx\n", vma->vm_start, vma->vm_pgoff<<PAGE_SHIFT);
}

void vma_close(struct vm_area_struct *vma)
{
    printk("[vma_close-success]\n");
}

struct page *vma_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
{
    struct page *pageptr;
    unsigned long baseaddr = addr;
    unsigned long virtaddr = vmf->virtual_address - vma->vm_start + baseaddr;
    
    pageptr = virt_to_page(virtaddr);
    if(!pageptr)
    {
        return VM_FAULT_SIGBUS;
    }
    get_page(pageptr);
    vmf->page=pageptr;
    
    printk("[vma_fault-success]\n");
    return pageptr;
}

static struct vm_operations_struct fault_vm_ops={
    .open = vma_open,
    .close = vma_close,
    .fault = vma_fault,
};

static int fault_mmap(struct file* filp, struct vm_area_struct *vma){
    unsigned long offset = vma->vm_pgoff<<PAGE_SHIFT;
    if(offset>=__pa(high_memory)||(filp->f_flags & O_SYNC))
        vma->vm_flags |= VM_IO;
    vma->vm_flags |= VM_RESERVED;

    vma->vm_ops = &fault_vm_ops;
    vma_open(vma);
    printk("[fault_mmap-success]\n");
    return 0;
}

// 设备的读操作
static int rwbuf_open(struct inode *inode, struct file *filp)
{
    printk("[rwbuf_open-success]\n");
    return 0;
}

// 设备的关闭
static int rwbuf_release(struct inode *inode, struct file *filp)
{
    printk("[rwbuf_release-success]\n");
    return 0;
}

// rwbuf_fops要在rwf_buf_init()前面声明,因为register_chrdev()函数要使用到
struct file_operations rwbuf_fops={
    open:rwbuf_open,
    release:rwbuf_release,
    mmap:fault_mmap,
};

// module_init()内的初始化函数:返回-1表示错误;返回0表示成功
static int __init rwbuf_init(void)
{
    // 表示注册成功与否:-1表示失败,0表示成功(同register_chrdev返回值)。初始化为-1
    int ret = -1;
    /*
    参数1:设备的种类,即主设备号
    参数2:设备的名称
    参数3:和VFS对接的接口,即上面的结构体变量
    */
    ret = register_chrdev(60, DEVICE_NAME, &rwbuf_fops);
    // 注册失败
    if (ret == -1)
    {
        printk("[rwbuf_init-register-failed]\n");
    }
    // 注册成功
    else
    {
        printk("[rwbuf_init-register-success]\n");
        
        int order = get_order(64*1024);
        printk("[rwbuf_init-order] %d\n",order);
        addr = __get_free_pages(GFP_KERNEL,order);

        printk("[rwbuf_init-get_free_pages-addr] %lu\n",addr);
    }
    // 返回ret(同register_chrdev返回值)
    return ret;
}

// module_exit()内的退出函数。
static int __exit rwbuf_exit(void)
{
    int order = get_order(64*1024);
    if(addr){
        free_pages(addr,order);
        printk("[rwbuf_exit-free_pages-success]\n");
    }else{
        printk("[rwbuf_exit-free_pages-error]\n");
    }
    
    unregister_chrdev(60, DEVICE_NAME);
    printk("[rwbuf_exit-success]\n");

    return 0;
}

// 内核模块入口,相当于main()函数,完成模块初始化
module_init(rwbuf_init);
// 卸载时调用的函数入口,完成模块卸载
module_exit(rwbuf_exit);
// GPL协议证书
MODULE_LICENSE("GPL");

(2)Makefile

gedit Makefile
内容:

obj-m := rwbuf.o
KERNELDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
modules:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
	rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions modules.order  Module.symvers

(3)编译

sudo make

2.创建设备文件(设备进入点)

(1)创建

sudo mknod /dev/rwbuf c 60 0

意思:
在/dev目录下创建一个名为rwbuf的设备文件
设备文件的类型是c(字符型设备)
主设备号是60
次设备号是0
(2)赋权
sudo chmod 777 /dev/rwbuf
更改设备的权限。没有读写权限的话,就会读出一堆无意义的乱码。

3.插入内核模块(加载设备驱动程序)

先清理一下缓存,不然一会就可能输出一大堆多余东西,影响到我们想要看到的输出东西
sudo dmesg -c
插入内核模块(加载设备驱动程序)
sudo insmod rwbuf.ko N=1000

查看是否成功
dmesg

在这里插入图片描述

4.测试用户应用程序(调用驱动程序)

(1)读写
gedit test.c

#include <stdio.h>
#include <stdlib.h>
// 为了open()中的O_RDWR
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <string.h>

// 定义设备进入点(设备名)
#define DEVICE_NAME "/dev/rwbuf"
// 定义大小
#define SHARE_MEM_SIZE 4096
#define SHARE_MEM_PAGE_COUNT 16

int main() 
{ 
    int fd; 
    char *ptrdata; 
    fd=open(DEVICE_NAME,O_RDWR|O_NDELAY); 
    if (fd == -1)
    {
        printf("[open-error]\n");
        return 0;
    }
    else
    {
        printf("[open-success]\n");
    }
    ptrdata=(char*)mmap(0, SHARE_MEM_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); 
    int loop;
    for(loop=0;loop<SHARE_MEM_PAGE_COUNT;loop++)
    { 
        printf("[%-10s----]\n",ptrdata+4096*loop); 
    } 
    munmap(ptrdata,SHARE_MEM_SIZE); 
    close(fd); 
    return 0; 
}

在这里插入图片描述

rmmod rwbuf
dmesg在这里插入图片描述

五、心得体会与建议

感觉最后应用程序不太会。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值