Linux设备驱动之mmap设备操作
1.mmap系統調用
void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset); 功能:負責把文件內容映射到進程的虛擬地址空間,通過對這段內存的讀取和修改來實現對文件的讀取和修改,而不需要再調用read和write;
參數:addr:映射的起始地址,設為NULL由系統指定;
len:映射到內存的文件長度;
prot:期望的內存保護標志,不能與文件的打開模式沖突。PROT_EXEC,PROT_READ,PROT_WRITE等;
flags:指定映射對象的類型,映射選項和映射頁是否可以共享。MAP_SHARED,MAP_PRIVATE等;
fd:由open返回的文件描述符,代表要映射的文件;
offset:開始映射的文件的偏移。
返回值:成功執行時,mmap()返回被映射區的指針。失敗時,mmap()返回MAP_FAILED。
mmap映射圖:
2.解除映射:
int munmap(void *start, size_t length);?
3.虛擬內存區域:
虛擬內存區域是進程的虛擬地址空間中的一個同質區間,即具有同樣特性的連續地址范圍。一個進程的內存映象由下面幾個部分組成:程序代碼、數據、BSS和棧區域,以及內存映射的區域。
linux內核使用vm_area_struct結構來描述虛擬內存區。其主要成員:
4.mmap設備操作:
映射一個設備是指把用戶空間的一段地址(虛擬地址區間)關聯到設備內存上,當程序讀寫這段用戶空間的地址時,它實際上是在訪問設備。
mmap方法是file_operations結構的成員,在mmap系統調用的發出時被調用。在此之前,內核已經完成了很多工作。
mmap設備方法所需要做的就是建立虛擬地址到物理地址的頁表(虛擬地址和設備的物理地址的關聯通過頁表)。
mmap如何完成頁表的建立?(兩種方法)
(1)使用remap_pfn_range一次建立所有頁表。
(2)使用nopage VMA方法每次建立一個頁表;
?
5.源碼分析:
(1)memdev.h
#ifndef _MEMDEV_H_ #define _MEMDEV_H_#ifndef MEMDEV_MAJOR #define MEMDEV_MAJOR 0 /*預設的mem的主設備號*/ #endif#ifndef MEMDEV_NR_DEVS #define MEMDEV_NR_DEVS 2 /*設備數*/ #endif#ifndef MEMDEV_SIZE #define MEMDEV_SIZE 4096 #endif /*mem設備描述結構體*/ struct mem_dev { char *data; unsigned long size; };#endif /* _MEMDEV_H_ */
(2)memdev.c
#include <linux/module.h> #include <linux/types.h> #include <linux/fs.h> #include <linux/errno.h> #include <linux/mm.h> #include <linux/sched.h> #include <linux/init.h> #include <linux/cdev.h> #include <asm/io.h> #include <asm/system.h> #include <asm/uaccess.h>#include <linux/kernel.h> #include "memdev.h" static int mem_major = MEMDEV_MAJOR;module_param(mem_major, int, S_IRUGO);struct mem_dev *mem_devp; /*設備結構體指針*/struct cdev cdev; /*文件打開函數*/ int mem_open(struct inode *inode, struct file *filp) {struct mem_dev *dev;/*獲取次設備號*/int num = MINOR(inode->i_rdev);if (num >= MEMDEV_NR_DEVS) return -ENODEV;dev = &mem_devp[num];/*將設備描述結構指針賦值給文件私有數據指針*/filp->private_data = dev;return 0; }/*文件釋放函數*/ int mem_release(struct inode *inode, struct file *filp) {return 0; } static int memdev_mmap(struct file*filp, struct vm_area_struct *vma) {struct mem_dev *dev = filp->private_data; /*獲得設備結構體指針*/vma->vm_flags |= VM_IO;vma->vm_flags |= VM_RESERVED;if (remap_pfn_range(vma,vma->vm_start,virt_to_phys(dev->data)>>PAGE_SHIFT, vma->vm_end - vma->vm_start, vma->vm_page_prot))return -EAGAIN;return 0; }/*文件操作結構體*/ static const struct file_operations mem_fops = {.owner = THIS_MODULE,.open = mem_open,.release = mem_release,.mmap = memdev_mmap, };/*設備驅動模塊加載函數*/ static int memdev_init(void) {int result;int i;dev_t devno = MKDEV(mem_major, 0);/* 靜態申請設備號*/if (mem_major)result = register_chrdev_region(devno, 2, "memdev");else /* 動態分配設備號 */{result = alloc_chrdev_region(&devno, 0, 2, "memdev");mem_major = MAJOR(devno);} if (result < 0)return result;/*初始化cdev結構*/cdev_init(&cdev, &mem_fops);cdev.owner = THIS_MODULE;cdev.ops = &mem_fops;/* 注冊字符設備 */cdev_add(&cdev, MKDEV(mem_major, 0), MEMDEV_NR_DEVS);/* 為設備描述結構分配內存*/mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL);if (!mem_devp) /*申請失敗*/{result = - ENOMEM;goto fail_malloc;}memset(mem_devp, 0, sizeof(struct mem_dev));/*為設備分配內存*/for (i=0; i < MEMDEV_NR_DEVS; i++) {mem_devp[i].size = MEMDEV_SIZE;mem_devp[i].data = kmalloc(MEMDEV_SIZE, GFP_KERNEL);memset(mem_devp[i].data, 0, MEMDEV_SIZE);}return 0;fail_malloc: unregister_chrdev_region(devno, 1);return result; }/*模塊卸載函數*/ static void memdev_exit(void) {cdev_del(&cdev); /*注銷設備*/kfree(mem_devp); /*釋放設備結構體內存*/unregister_chrdev_region(MKDEV(mem_major, 0), 2); /*釋放設備號*/ }MODULE_AUTHOR("David Xie"); MODULE_LICENSE("GPL");module_init(memdev_init); module_exit(memdev_exit);
(3)app-mmap.c
#include <stdio.h> #include<sys/types.h> #include<sys/stat.h> #include<fcntl.h> #include<unistd.h> #include<sys/mman.h>int main() {int fd;char *start;//char buf[100]; char *buf;/*打開文件*/fd = open("/dev/memdev0",O_RDWR);buf = (char *)malloc(100);memset(buf, 0, 100);start=mmap(NULL,100,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);/* 讀出數據 */strcpy(buf,start);sleep (1);printf("buf 1 = %s\n",buf); /* 寫入數據 */strcpy(start,"Buf Is Not Null!");memset(buf, 0, 100);strcpy(buf,start);sleep (1);printf("buf 2 = %s\n",buf);munmap(start,100); /*解除映射*/free(buf);close(fd); return 0; }
測試步驟:
(1)編譯安裝內核模塊:insmod memdev.ko
(2)查看設備名、主設備號:cat /proc/devices
(3)手工創建設備節點:mknod ?/dev/memdev0 ?c ?*** ?0
查看設備文件是否存在:ls -l /dev/* | grep memdev
(4)編譯下載運行應用程序:./app-mmap
結果:buf 1 =?
buf 2 =?Buf Is Not Null!
總結:mmap設備方法實現將用戶空間的一段內存關聯到設備內存上,對用戶空間的讀寫就相當于對字符設備的讀寫;不是所有的設備都能進行mmap抽象,比如像串口和其他面向流的設備就不能做mmap抽象。
總結
以上是生活随笔為你收集整理的Linux设备驱动之mmap设备操作的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux高级字符设备之Poll操作
- 下一篇: Linux设备驱动之I/O端口与I/O内