您好,登錄后才能下訂單哦!
今天就跟大家聊聊有關如何解析Linux驅動中mmap內存映射,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結了以下內容,希望大家根據這篇文章可以有所收獲。
上圖說了,mmap是操作這些設備的一種方法,所謂操作設備,比如IO端口(點亮一個LED)、LCD控制器、磁盤控制器,實際上就是往設備的物理地址讀寫數據。
但是,由于應用程序不能直接操作設備硬件地址,所以操作系統提供了這樣的一種機制——內存映射,把設備地址映射到進程虛擬地址,mmap就是實現內存映射的接口。
操作設備還有很多方法,如ioctl、ioremap
mmap的好處是,mmap把設備內存映射到虛擬內存,則用戶操作虛擬內存相當于直接操作設備了,省去了用戶空間到內核空間的復制過程,相對IO操作來說,增加了數據的吞吐量。
既然mmap是實現內存映射的接口,那么內存映射是什么呢?看下圖
驅動程序運行在內核空間,所以驅動程序是面向所有進程的。
用戶空間切換到內核空間有兩種方法:
(1)系統調用,即軟中斷
(2)硬件中斷
了解了什么是虛擬地址空間,那么虛擬地址空間里面裝的是什么?看下圖
現在已經知道了內存映射是把設備地址映射到進程空間地址(注意:并不是所有內存映射都是映射到進程地址空間的,ioremap是映射到內核虛擬空間的,mmap是映射到進程虛擬地址的),實質上是分配了一個vm_area_struct結構體加入到進程的地址空間,也就是說,把設備地址映射到這個結構體,映射過程就是驅動程序要做的事了。
以字符設備驅動為例,一般對字符設備的操作都如下框圖
以下是mmap_driver.c的源代碼
[cpp] view plain copy
//所有的模塊代碼都包含下面兩個頭文件
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h> //定義dev_t類型
#include <linux/cdev.h> //定義struct cdev結構體及相關操作
#include <linux/slab.h> //定義kmalloc接口
#include <asm/io.h>//定義virt_to_phys接口
#include <linux/mm.h>//remap_pfn_range
#include <linux/fs.h>
#define MAJOR_NUM 990
#define MM_SIZE 4096
static char driver_name[] = "mmap_driver1";//驅動模塊名字
static int dev_major = MAJOR_NUM;
static int dev_minor = 0;
char *buf = NULL;
struct cdev *cdev = NULL;
static int device_open(struct inode *inode, struct file *file)
{
printk(KERN_ALERT"device open\n");
buf = (char *)kmalloc(MM_SIZE, GFP_KERNEL);//內核申請內存只能按頁申請,申請該內存以便后面把它當作虛擬設備
return 0;
}
static int device_close(struct inode *indoe, struct file *file)
{
printk("device close\n");
if(buf)
{
kfree(buf);
}
return 0;
}
static int device_mmap(struct file *file, struct vm_area_struct *vma)
{
vma->vm_flags |= VM_IO;//表示對設備IO空間的映射
vma->vm_flags |= VM_RESERVED;//標志該內存區不能被換出,在設備驅動中虛擬頁和物理頁的關系應該是長期的,應該保留起來,不能隨便被別的虛擬頁換出
if(remap_pfn_range(vma,//虛擬內存區域,即設備地址將要映射到這里
vma->vm_start,//虛擬空間的起始地址
virt_to_phys(buf)>>PAGE_SHIFT,//與物理內存對應的頁幀號,物理地址右移12位
vma->vm_end - vma->vm_start,//映射區域大小,一般是頁大小的整數倍
vma->vm_page_prot))//保護屬性,
{
return -EAGAIN;
}
return 0;
}
static struct file_operations device_fops =
{
.owner = THIS_MODULE,
.open = device_open,
.release = device_close,
.mmap = device_mmap,
};
static int __init char_device_init( void )
{
int result;
dev_t dev;//高12位表示主設備號,低20位表示次設備號
printk(KERN_ALERT"module init2323\n");
printk("dev=%d", dev);
dev = MKDEV(dev_major, dev_minor);
cdev = cdev_alloc();//為字符設備cdev分配空間
printk(KERN_ALERT"module init\n");
if(dev_major)
{
result = register_chrdev_region(dev, 1, driver_name);//靜態分配設備號
printk("result = %d\n", result);
}
else
{
result = alloc_chrdev_region(&dev, 0, 1, driver_name);//動態分配設備號
dev_major = MAJOR(dev);
}
if(result < 0)
{
printk(KERN_WARNING"Cant't get major %d\n", dev_major);
return result;
}
cdev_init(cdev, &device_fops);//初始化字符設備cdev
cdev->ops = &device_fops;
cdev->owner = THIS_MODULE;
result = cdev_add(cdev, dev, 1);//向內核注冊字符設備
printk("dffd = %d\n", result);
return 0;
}
static void __exit char_device_exit( void )
{
printk(KERN_ALERT"module exit\n");
cdev_del(cdev);
unregister_chrdev_region(MKDEV(dev_major, dev_minor), 1);
}
module_init(char_device_init);//模塊加載
module_exit(char_device_exit);//模塊退出
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ChenShengfa");
下面是測試代碼test_mmap.c
[cpp] view plain copy
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <string.h>
int main( void )
{
int fd;
char *buffer;
char *mapBuf;
fd = open("/dev/mmap_driver", O_RDWR);//打開設備文件,內核就能獲取設備文件的索引節點,填充inode結構
if(fd<0)
{
printf("open device is error,fd = %d\n",fd);
return -1;
}
/*測試一:查看內存映射段*/
printf("before mmap\n");
sleep(15);//睡眠15秒,查看映射前的內存圖cat /proc/pid/maps
buffer = (char *)malloc(1024);
memset(buffer, 0, 1024);
mapBuf = mmap(NULL, 1024, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);//內存映射,會調用驅動的mmap函數
printf("after mmap\n");
sleep(15);//睡眠15秒,在命令行查看映射后的內存圖,如果多出了映射段,說明映射成功
/*測試二:往映射段讀寫數據,看是否成功*/
strcpy(mapBuf, "Driver Test");//向映射段寫數據
memset(buffer, 0, 1024);
strcpy(buffer, mapBuf);//從映射段讀取數據
printf("buf = %s\n", buffer);//如果讀取出來的數據和寫入的數據一致,說明映射段的確成功了
munmap(mapBuf, 1024);//去除映射
free(buffer);
close(fd);//關閉文件,最終調用驅動的close
return 0;
}
下面是makefile文件
[plain] view plain copy
ifneq ($(KERNELRELEASE),)
obj-m := mmap_driver.o
else
KDIR := /lib/modules/3.2.0-52-generic/build
all:
make -C $(KDIR) M=$(PWD) modules
clean:
rm -f *.ko *.o *.mod.o *.mod.c *~ *.symvers *.order
endif
下面命令演示一下驅動程序的編譯、安裝、測試過程(注:其他用戶在mknod之后還需要chmod改變權限)
# make //編譯驅動
# insmod mmap_driver.ko //安裝驅動
# mknod /dev/mmap_driver c 999 0 //創建設備文件
# gcc test_mmap.c -o test.o //編譯應用程序
# ./test.o //運行應用程序來測試驅動程序
拓展:
關于這個過程,涉及一些術語
(1)設備文件:linux中對硬件虛擬成設備文件,對普通文件的各種操作均適用于設備文件
(2)索引節點:linux使用索引節點來記錄文件信息(如文件長度、創建修改時間),它存儲在磁盤中,讀入內存后就是一個inode結構體,文件系統維護了一個索引節點的數組,每個元素都和文件或者目錄一一對應。
(3)主設備號:如上面的999,表示設備的類型,比如該設備是lcd還是usb等
(4)次設備號:如上面的0,表示該類設備上的不同設備
(5)文件(普通文件或設備文件)的三個結構
①文件操作:struct file_operations
②文件對象:struct file
③文件索引節點:struct inode
關于驅動程序中內存映射的實現,先了解一下open和close的流程
(1)設備驅動open流程
(3)設備驅動mmap流程
由open和close得知,同理,應用程序調用mmap最終也會調用到驅動程序中mmap方法
①應用程序test.mmap.c中mmap函數
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、PROT_NONE)
flags:參考網絡資料
fd:文件描述符
offset:文件映射開始偏移量
②驅動程序的mmap_driver.c中mmap函數
上面說了,mmap的主要工作是把設備地址映射到進程虛擬地址,也即是一個vm_area_struct的結構體,這里說的映射,是一個很懸的東西,那它在程序中的表現是什么呢?——頁表,沒錯,就是頁表,映射就是要建立頁表。進程地址空間就可以通過頁表(軟件)和MMU(硬件)映射到設備地址上了
virt_to_phys(buf),buf是在open時申請的地址,這里使用virt_to_phys把buf轉換成物理地址,是模擬了一個硬件設備,即把虛擬設備映射到虛擬地址,在實際中可以直接使用物理地址。
總結
①從以上看到,內核各個模塊錯綜復雜、相互交叉
②單純一個小小驅動模塊,就涉及了進程管理(進程地址空間)、內存管理(頁表與頁幀映射)、虛擬文件系統(structfile、structinode)
③并不是所有設備驅動都可以使用mmap來映射,比如像串口和其他面向流的設備,并且必須按照頁大小進行映射。
看完上述內容,你們對如何解析Linux驅動中mmap內存映射有進一步的了解嗎?如果還想了解更多知識或者相關內容,請關注億速云行業資訊頻道,感謝大家的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。