Linux驱动----mmap系统调用

在介绍mmap之前,我们首先要了解,如果没有mmap,在我们对一个设备进行操作时,内核中其实有映射一份该设备的物理内存地址,这是一份虚拟地址,但是这是属于内核的,我们的应用程序是无法直接来对其进行操作的,如果操作起来,势必会消耗很大的资源。因此,为了方便应用程序对设备的操作,于是就有了mmap。

mmap其实是一个字符设备提供的一个接口,通过这个接口,我们可以将原本设备映射到内核空间中的虚拟地址,再映射一份到用户空间,这样就可以通过直接操作用户空间映射的虚拟空间来实现对设备物理内存的访问,提高了操作效率。


有关mmap的具体使用方法,接下来通过一个小例子来详细说明下。

我通过在内核中申请一片空间,然后通过mmap映射到用户空间中,在用户空间中对映射而来的虚拟地址空间进行一个写入值的操作,然后在内核中使用printk打印信息查看是否有变化,以此来验证mmap的功能。

下面是全部的驱动代码

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/mm.h>
#include <linux/memory.h>
#include <linux/slab.h>
#include <linux/device.h>
MODULE_LICENSE("GPL");


#define KBUF_MAX 8
char kbuf[KBUF_MAX] = "abcdefg";
dev_t devnumber;  //字符设备的设备号,先申请一个空间
unsigned baseminor = 0;  //次设备号,自己设定为0号
unsigned devicecount = 1;  //设备的数量

char  *kptr = NULL;    //此处定义一个指针,后面用它去在内核中申请一片空间


struct cdev mydevice;  //代表着一个设备的驱动程序
typedef struct {
	unsigned int conaddr;
	unsigned int dataddr;
	unsigned int *pcon;
	unsigned int *pdat;
	unsigned int ledpin;
}LEDRES;

LEDRES myledres[1] = {//led3------- GPX1_0 
	{
		.conaddr = 0x11000c20,
		.dataddr = 0x11000c24,
		.pcon = NULL,
		.pdat = NULL,
		.ledpin = 0,
	},

};


ssize_t dev_read(struct file *f,char __user *buf,size_t length ,loff_t *pos)
{
  long n;
  printk("length = %lu,*pos = %lld\n ",length,*pos);
  
  n = copy_to_user(buf,kbuf,length);
  printk("after copy_to_user , n = %ld\n",n);
  
  printk("dev read...\n");
  return length-n;
}

ssize_t dev_write(struct file *f,const char __user *buf,size_t length ,loff_t *pos)
{
  long n;
  if(length > KBUF_MAX)
  {
    length = KBUF_MAX;
  }
  n = copy_from_user(kbuf,buf,length);
  printk("after copy_from_user , n = %ld\n ,kbuf = %s\n",n,kbuf);
  
  
  printk("dev write...\n");
  return length - n;
}

int dev_open(struct inode *nod ,struct file *f)
{
  
	printk("dev open ...\n");
	return 0;
}

int dev_close(struct inode *nod ,struct file *f)
{
  printk(" in kernel, kptr = %s\n",kptr);


	printk("dev close ...\n");
	return 0;
}



int dev_mmap (struct file *f, struct vm_area_struct *vvv)
{
	remap_pfn_range(vvv,vvv->vm_start,virt_to_phys(kptr)>>12,vvv->vm_end-vvv->vm_start,vvv->vm_page_prot);
	
	return 0;
}

struct file_operations dev_ops = {

  .release = dev_close,
  .read = dev_read,
  .write = dev_write,
  .open = dev_open,
  .mmap = dev_mmap,
};


//int init_module()
static int __init test_init(void)
{
       
    int ret = -1;
    printk("dev, init .....\n");
    ret = alloc_chrdev_region(&devnumber,baseminor,devicecount,"test");  //动态分配设备号
    //在这里,第一个参数是传入为了将系统给分配的设备号给带出来
    //此处的,第二个、第三个、都是传入的给定的值,自己限定的
    //第四个也是自己给定的名字
    if(ret < 0)
      goto ALLOC_ERROR;
          
    printk("devnumber = %x , majorno = %x , minorno = %x\n",devnumber,MAJOR(devnumber),MINOR(devnumber));
        
    cdev_init(&mydevice,&dev_ops);
    ret = cdev_add(&mydevice, devnumber,devicecount);
    //添加到系统字符设备列表中
        
    if(ret < 0 ) 
      goto CDEV_ADD_ERROR;
        
    kptr = kmalloc(4096,GFP_KERNEL); 
	memset(kptr,0,4096);	
	printk("dev, init ..... kptr = %s\n",kptr);

             
    return 0;

CDEV_ADD_ERROR:
    cdev_del(&mydevice);

ALLOC_ERROR:  
    unregister_chrdev_region(devnumber,devicecount); 
  
    return -1;     
        
}
static void __exit test_exit(void)
{
	kfree(kptr);//释放内核里kmalloc 申请的内存
    cdev_del(&mydevice);
    unregister_chrdev_region(devnumber,devicecount); 
    printk("dev,exit...\n");
}
module_init(test_init);
module_exit(test_exit);

应用程序代码

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>


#define MAX_LENGTH   8
char rbuf[MAX_LENGTH] = {0};
char wbuf[MAX_LENGTH] = "1234567";

int main()
{
	int fd;
	int count;
	char *uptr;
 
	fd = open("/dev/test",O_RDWR);
	perror("open:");


	count = read(fd,rbuf,MAX_LENGTH);
	perror("read:");

	printf(" count = %d,rbuf = %s\n",count,rbuf);


	count = write(fd, wbuf, strlen(wbuf));
	printf("write, count = %d\n",count);
	perror("write:");

//对由内核空间映射地址的操作

	uptr = mmap(NULL,4096,PROT_READ|PROT_WRITE,MAP_SHARED, fd, 0);
	strcpy(uptr,"hijklmn");

	close(fd);
	perror("close:");

	return 0;

		
}

在驱动的入口函数处,用指针kptr去指向了一片有kmalloc创建的空间,清空后并打印输出了其值,以便后续做比较

kptr = kmalloc(4096,GFP_KERNEL); 
memset(kptr,0,4096);	
printk("dev, init ..... kptr = %s\n",kptr);

驱动中具体实现映射,主要是调用了remap_pfn_range函数,将其封装在dev_mmap函数之中,并在结构体struct file_operations dev_ops之中初始化。

int dev_mmap (struct file *f, struct vm_area_struct *vvv)
{
	remap_pfn_range(vvv,vvv->vm_start,virt_to_phys(kptr)>>12,vvv->vm_end-vvv->vm_start,vvv->vm_page_prot);
	
	return 0;
}
struct file_operations dev_ops = {

  .release = dev_close,
  .read = dev_read,
  .write = dev_write,
  .open = dev_open,
  .mmap = dev_mmap,
};

 有关remap_pfn_range函数,其原型如下

int remap_pfn_range(struct vm_area_struct *vma, unsigned long virt_addr, unsigned long pfn, unsigned long size, pgprot_t prot);

参数1:vma是用来描述一片映射区域的结构体指针

参数2:addr是用户指定的映射后的虚拟地址起始地址,若用户未指定,则由内核指定

参数3:物理内存所对应的页框号(将物理地址除以页大小的所得值)

参数4:想要映射的空间大小

参数5:内存区域的访问权限

在用户空间中调用mmap函数,或得映射地址指针,并对所得区域进行赋值

uptr = mmap(NULL,4096,PROT_READ|PROT_WRITE,MAP_SHARED, fd, 0);
strcpy(uptr,"hijklmn");

执行下面这三步 

下面来看看效果如何,使用dmesg | tail 查看内核缓冲区信息

起初

后来

 效果理想!

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Linux下,mmap函数可以用来将文件或设备的一部分物理内存映射到进程的虚拟地址空间中,从而实现进程和文件或设备的直接交互。使用mmap函数可以提高文件或设备的读写效率,避免了频繁的系统调用和缓冲区的拷贝。 mmap函数的原型为: ```c void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); ``` 各个参数的含义如下: - addr:映射区域的首地址,一般设为NULL,由系统自动分配。 - length:映射区域的长度,单位是字节。 - prot:映射区域的保护模式,可以是PROT_READ、PROT_WRITE或PROT_EXEC的组合。 - flags:映射区域的标志,可以是MAP_SHARED、MAP_PRIVATE、MAP_FIXED等的组合。 - fd:需要映射的文件描述符。 - offset:文件偏移量,表示从文件的哪个位置开始映射。 mmap函数返回映射区域的首地址或者MAP_FAILED,表示映射失败。 使用mmap函数时,需要先打开文件或设备,并获取相应的文件描述符。然后,调用mmap函数将文件或设备的一部分物理内存映射到进程的虚拟地址空间中。最后,使用指针来访问映射区域的数据,进行读写操作。使用完映射区域后,需要调用munmap函数解除映射关系。 需要注意的是,使用mmap函数进行读写操作时,需要考虑到内存对齐和边界问题,否则可能会出现读写错误。同时,对于设备文件的映射,还需要考虑到设备驱动程序的特殊要求,比如缓冲区的大小和对齐方式等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

翔在天上飞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值