linux 内存映射.docx
- 文档编号:27827777
- 上传时间:2023-07-05
- 格式:DOCX
- 页数:14
- 大小:116.15KB
linux 内存映射.docx
《linux 内存映射.docx》由会员分享,可在线阅读,更多相关《linux 内存映射.docx(14页珍藏版)》请在冰豆网上搜索。
linux内存映射
Linux的内存映射
在讲解内存映射之前,不得不去探讨Linux内存管理方面的知识。
需要说明的是,我们并不需要深入的理解Linux虚拟内存才能去实现Linux的内存映射,所以对于Linux内存管理方面的知识也仅限于最基础的概念。
一、Linux的内存管理
Linux的内存管理子系统是采用请求调页式的虚拟存储器技术实现的,有关虚拟存储器方面的知识可以参考《深入理解计算机系统》第二版的第9章内容,在这里就不做说明。
1、Linux进程的虚拟空间及其划分
在32位硬件平台上,Linux的逻辑地址为32位,因此,每个进程的虚拟地址空间为4GB,在4GB的空间中,操作系统占用了高端的1GB,而低端的3GB则留给用户程序使用。
如下图所示:
1) Linux内核虚拟存储器
Linux中1GB的内核虚拟存储器空间又被划分为物理内存映射区、虚拟内存分配区、高端页面映射区、专用页面映射区和系统保留映射区这几个区域。
一般情况下,物理内存映射区最大长度为896MB,系统的物理内存被顺序映射到物理内存映射区中。
当系统物理内存大于896MB时,超过系统物理内存的那部分内存称为高端内存(小于896MB的系统物理内存称为常规内存),内核在存取高端内存时必须将它们映射到高端内存映射区中。
下图可以反映出Linux内核虚拟存储器与物理内存之间的映射关系。
注意:
物理内存中0~896MB区域通常由内核使用,当然内核不用时用户程序可以使用;896MB以上的区域通常由用户程序来使用。
2) Linux用户虚拟存储器
Linux用户虚拟存储器总是通过页表访问内存,决不会直接访问。
如下图所示:
2、进程空间的描述
内核为系统中的每个进程维护一个单独的任务结构task_struct。
任务结构中的元素包含或者指向内核运行该进程所需要的所有信息(例如,PID、指向用户栈的指针、可执行目标文件的名字以及程序计数器)。
task_struct中的一个条目指向mm_struct,它描述进程使用的地址空间,我们感兴趣的两个字段是pgd和mmap,其中pgd指向第一级页表(页全局目录)的基址,而mmap指向一个vm_area_struct(区域结构)的链表,每个vm_area_struct结构描述的是进程的一个用户区。
如下图所示:
二、Linux的内存映射
当可执行文件准备运行时,可执行文件的内容仅仅映射到了对应进程虚拟地址空间中,而并没有调入物理内存。
当程序开始运行并使用到这部分时,Linux才通过缺页中断把它们从磁盘上调入内存。
这种将文件连接到进程虚拟地址空间的过程称为内存映射。
1、vm_area_struct结构
struct vm_area_struct {
struct mm_struct * vm_mm; /*所处的地址空间*/
unsigned long vm_start; /*开始的虚拟地址*/
unsigned long vm_end; /*结束的虚拟地址*/
pgprot_t vm_page_prot; /*访问权限*/
unsigned long vm_flags; /*标志,VM_IO和VM_RESERVED等*/
//操作VMA的函数集
struct vm_operations_struct * vm_ops;
unsigned long vm_pgoff; /*偏移(页帧号)*/
struct file * vm_file; /*指向该区域(如果存在的话)相关联的file结构指针*/
void * vm_private_data; /*驱动程序用来保存自身信息的成员*/
};
vm_operations_struct结构的定义如下:
struct vm_operations_struct {
//打开VMA的函数
void (*open)(struct vm_area_struct * area);
//关闭VMA的函数
void (*close)(struct vm_area_struct * area);
//访问的页不在内存时调用
struct page * (*nopage)(struct vm_area_struct * area, unsigned long address, int *type);
unsigned long (*nopfn)(struct vm_area_struct * area, unsigned long address);
//驱动程序不必实现populate方法
int (*populate)(struct vm_area_struct * area, unsigned long address, unsigned long len, pgprot_t prot, unsigned long pgoff, int nonblock);
};
2、内存映射
一般情况下,用户空间是不可能也不应该直接访问设备的,但是,设备驱动程序中可实现mmap()函数,这个函数可使得用户空间能直接访问设备的物理地址。
实际上,mmap()实现了这样的一个映射过程,它将用户空间的一段内存与设备内存关联,当用户访问用户空间的这段地址范围时,实际上会转化为对设备的访问。
3、mmap设备操作
mmap方法是file_operations结构的一部分,mmap设备方法所需要做到就是建立虚拟地址到物理地址的页表。
执行mmap系统调用时将调用该方法。
使用mmap,内核在调用实际函数之前,就能完成大量的工作,因此该方法的原型与系统调用有着很大的不同。
系统调用有着以下的声明:
caddr_t mmap(caddr_t addr, size_t len, int prot, int flags, int fd, off_t offset);
addr:
指定文件应被映射到用户空间的起始地址,这样,选择起始地址的任务将由内核完成,而函数的返回值就是映射到用户空间的地址。
其类型caddr_t实际上就是void *。
len:
映射到调用用户空间的字节数,它从被映射文件开头offset个字节开始算起,offset参数一般设为0,表示从文件头开始映射。
prot:
指定访问权限,PROT_READ(可读)、PROT_WRITE(可写)、PROT_EXEC(可执行)和PROT_NONE(不可访问)。
flags:
MAP_SHARED , MAP_PRIVATE , MAP_FIXED,其中,MAP_SHARED , MAP_PRIVATE必选其一,而MAP_FIXED则不推荐使用。
fd:
为文件描述符,一般由open()返回,fd也可以指定为-1,此时需指定flags参数中的MAP_ANON,表明进行的是匿名映射。
但是文件操作声明如下:
int (*mmap) (struct file *, struct vm_area_struct *);
vma包含了用于访问设备的虚拟地址的信息,因此大量的工作由内核完成。
为了执行mmap,驱动程序只需要为该地址返回建立合适的页表,并将vma->vm_ops替换为一系列的新操作就可以了。
注意:
当用户调用mmap()的时候,内核会进行如下的处理:
① 在进程的虚拟空间查找一块VMA。
② 将这块VMA进行映射。
③ 如果设备驱动程序或者文件系统的file_operations定义了mmap()操作,则调用它。
④ 将这个VMA插入进程的VMA链表中。
有两种建立页表的方法:
使用remap_pfn_range函数一次全部建立;或者通过nopage VMA方法每次建立一个页表。
本文只考虑第一种情况。
使用remap_pfn_range
remap_pfn_range负责为一段物理地址建立新的页表,它有如下的原型:
int remap_pfn_range(struct vm_area_struct *vma, unsigned long from,
unsigned long to, unsigned long size, pgprot_t prot)
vma:
虚拟内存区域,在一定范围内的页将被映射到该区域内。
from:
表示内存映射开始处的虚拟地址。
to:
虚拟地址应该映射到的物理地址的页帧号。
size:
以字节为单位,被重新映射的区域大小。
prot:
新页所要求的保护属性。
三、驱动程序代码
#include
#include
#include
#include
#include
#include
#include
#include
#define MAX_SIMPLE_DEV 1
static int simple_major=0; /*定义主设备号*/
//打开设备文件时被调用
static int simple_open(struct inode *inode,struct file *filp)
{
return 0;
}
//关闭设备文件时被调用
static int simple_release(struct inode *inode,struct file *filp)
{
return 0;
}
//VMA打开函数
void simple_vma_open(struct vm_area_struct *vma)
{
printk(KERN_NOTICE"Simple VMA open,virt %lx,phys %lx\n",vma->vm_start,vma->vm_pgoff< } //VMA关闭函数 void simple_vma_close(struct vm_area_struct *vma) { printk(KERN_NOTICE"Simple VMA close.\n"); } static struct vm_operations_struct simple_remap_vm_ops={ /* *在内核生成一个VMA后,会调用VMA的open()函数,但是,当用户进行mmap()系统调用后, *尽管VMA在设备驱动文件操作结构体的mmap()被调用前就已产生,内核却不会调用VMA的 *open()函数,通常需要在驱动的mmap()函数显示的调用 */ .open = simple_vma_open, .close = simple_vma_close, }; static int simple_remap_mmap(struct file *filp,struct vm_area_struct *vma) { //建立页表 if(remap_pfn_range(vma,vma->vm_start,vma->vm_pgoff,vma->vm_end-vma->vm_start,vma->vm_page_prot)) return -EAGAIN; //填充VMA结构体中的vm_operations_struct指针 vma->vm_ops=&simple_remap_vm_ops; /* *对simple_vma_open函数的显示调用, */ simple_vma_open(vma); return 0; } //定义文件操作结构体 static struct file_operations simple_remap_ops={ .owner = THIS_MODULE, .open = simple_open, .release = simple_release, .mmap = simple_remap_mmap, }; static void simple_setup_cdev(struct cdev *dev,int minor,struct file_operations *fops) { int err,devno=MKDEV(simple_major,minor); //静态初始化cdev cdev_init(dev,fops); dev->owner=THIS_MODULE; //注册设备 err=cdev_add(dev,devno,1); if(err) printk(KERN_NOTICE"Error %d adding simple%d",err,minor); } static struct cdev SimpleDevs[MAX_SIMPLE_DEV]; /*静态的定义两个设备*/ static int simple_init(void) { int result; dev_t dev=MKDEV(simple_major,0); /*得到设备号*/ if(simple_major) result=register_chrdev_region(dev,2,"simple"); /*静态的分配设备号*/ else { result=alloc_chrdev_region(&dev,0,2,"simple"); /*动态的分配设备号*/ simple_major=MAJOR(dev); /*得到主设备号*/ } if(result<0) { printk(KERN_NOTICE"simple: unable to get major %d\n",simple_major); return result; } //设置两个设备 simple_setup_cdev(SimpleDevs,0,&simple_remap_ops); return 0; } static void simple_cleanup(void) { cdev_del(SimpleDevs); /*注销字符设备*/ unregister_chrdev_region(MKDEV(simple_major,0),2); /*释放设备号*/ } module_init(simple_init); module_exit(simple_cleanup); MODULE_AUTHOR("chenqi"); MODULE_LICENSE("GPL"); 四、测试程序代码 #include #include #include int main(int argc,char **argv) { char *fname; FILE *f; unsigned long offset,len; void *address; //用于判断输入的方式是否正确 if(argc! =4 || sscanf(argv[2],"%li",&offset)! =1 || sscanf(argv[3],"%li",&len)! =1) { fprintf(stderr, "%s: Usage \"%s argv[0]); exit (1); } fname=argv[1]; /*表示设备名*/ if(! (f=fopen(fname,"r"))) /*打开设备文件*/ { fprintf(stderr,"%s: %s: %s\n",argv[0],fname,strerror(errno)); exit (1); } //将设备地址映射到用户空间 address=mmap(0, len, PROT_READ, MAP_FILE | MAP_PRIVATE, fileno(f), offset); if(address==(void *)-1) { fprintf(stderr,"%s: mmap(): %s\n",argv[0],strerror(errno)); exit (1); } fclose(f); /*关闭由fopen()函数打开的文件*/ fprintf(stderr, "mapped \"%s\" from %lu (0x%08lx) to %lu (0x%08lx)\n", fname, offset, offset, offset+len, offset+len); // fwrite(address, 1, len, stdout); return 0; }
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- linux 内存映射 内存 映射
![提示](https://static.bdocx.com/images/bang_tan.gif)