您的位置:首页 > 其它

LDD3源码分析之内存映射

2012-08-19 09:09 330 查看
转载自/article/1358159.html

作者:刘昊昱

博客:http://blog.csdn.net/liuhaoyutz

编译环境:Ubuntu10.10

内核版本:2.6.32-38-generic-pae

LDD3源码路径:examples/simple/

本文分析LDD3第十五章介绍的内存映射模块simple。


一、simple模块编译

在2.6.32-38-generic-pae内核下编译simple模块时,会遇到一些问题,下面列出遇到的问题及解决办法。

执行make编译simple模块,会出现如下错误:





修改Makefile文件,把CFLAGS改为EXTRA_CFLAGS即可解决这个问题。再次编译,出现如下错误:





修改simple.c把第18行#include<linux/config.h>屏蔽掉,即可解决第一个错误,再次编译,出现如下错误:





simple.c的第115行,NOPAGE_SIGBUS宏在新的内核中已经不存在了,在2.6.10中该宏被定义为NULL,所以这里把115行改为

[cpp]viewplaincopy115return0;

再次编译,出现如下错误:





这是因为在新的内核中vm_operations_struct结构体的nopage函数已经被fault函数代替,所以把128行改为:

[cpp]viewplaincopy128.fault=simple_vma_nopage,

同时,按照fault的函数原型,重新改写simple_vma_nopage函数如下:

[cpp]viewplaincopy102intsimple_vma_nopage(structvm_area_struct*vma,
103structvm_fault*vmf)
104{
105structpage*pageptr;
106unsignedlongoffset=vma->vm_pgoff<<PAGE_SHIFT;
107unsignedlongphysaddr=(unsignedlong)vmf->virtual_address-vma->vm_start+offset;
108unsignedlongpageframe=physaddr>>PAGE_SHIFT;
109
110//Eventuallyremovetheseprintks
111printk(KERN_NOTICE"----Nopage,off%lxphys%lx\n",offset,physaddr);
112printk(KERN_NOTICE"VAis%p\n",__va(physaddr));
113printk(KERN_NOTICE"Pageat%p\n",virt_to_page(__va(physaddr)));
114if(!pfn_valid(pageframe))
115return0;
116pageptr=pfn_to_page(pageframe);
117printk(KERN_NOTICE"page->index=%ldmapping%p\n",pageptr->index,pageptr->mapping);
118printk(KERN_NOTICE"Pageframe%ld\n",pageframe);
119get_page(pageptr);
120//if(type)
121//*type=VM_FAULT_MINOR;
122returnVM_FAULT_NOPAGE;
123}

再次编译,模块编译成功,如下图所示:






二、simple模块分析

首先来看simple模块初始化函数simple_init:

[cpp]viewplaincopy197/*
198*Modulehousekeeping.
199*/
200staticintsimple_init(void)
201{
202intresult;
203dev_tdev=MKDEV(simple_major,0);
204
205/*Figureoutourdevicenumber.*/
206if(simple_major)
207result=register_chrdev_region(dev,2,"simple");
208else{
209result=alloc_chrdev_region(&dev,0,2,"simple");
210simple_major=MAJOR(dev);
211}
212if(result<0){
213printk(KERN_WARNING"simple:unabletogetmajor%d\n",simple_major);
214returnresult;
215}
216if(simple_major==0)
217simple_major=result;
218
219/*Nowsetuptwocdevs.*/
220simple_setup_cdev(SimpleDevs,0,&simple_remap_ops);
221simple_setup_cdev(SimpleDevs+1,1,&simple_nopage_ops);
222return0;
223}

203-217行,分配设备编号。

220行,创建字符设备,文件操作函数集是simple_remap_ops,使用remap_pfn_range映射内存。

221行,创建字符设备,文件操作函数集是simple_nopage_ops,使用nopage映射内存。

simple_setup_cdev函数定义如下:

[cpp]viewplaincopy145/*
146*Setupthecdevstructureforadevice.
147*/
148staticvoidsimple_setup_cdev(structcdev*dev,intminor,
149structfile_operations*fops)
150{
151interr,devno=MKDEV(simple_major,minor);
152
153cdev_init(dev,fops);
154dev->owner=THIS_MODULE;
155dev->ops=fops;
156err=cdev_add(dev,devno,1);
157/*Failgracefullyifneedbe*/
158if(err)
159printk(KERN_NOTICE"Error%daddingsimple%d",err,minor);
160}

simple_setup_cdev函数关联字符设备文件操作函数集,并向内核注册字符设备。

下面先来看simple_remap_ops文件操作函数集:

[cpp]viewplaincopy166/*Device0usesremap_pfn_range*/
167staticstructfile_operationssimple_remap_ops={
168.owner=THIS_MODULE,
169.open=simple_open,
170.release=simple_release,
171.mmap=simple_remap_mmap,
172};

simple_open和simple_release函数的实现都是简单返回0.

[cpp]viewplaincopy43staticintsimple_open(structinode*inode,structfile*filp)
44{
45return0;
46}

52staticintsimple_release(structinode*inode,structfile*filp)
53{
54return0;
55}

simple_remap_mmap函数的实现如下:

[cpp]viewplaincopy85staticintsimple_remap_mmap(structfile*filp,structvm_area_struct*vma)
86{
87if(remap_pfn_range(vma,vma->vm_start,vma->vm_pgoff,
88vma->vm_end-vma->vm_start,
89vma->vm_page_prot))
90return-EAGAIN;
91
92vma->vm_ops=&simple_remap_vm_ops;
93simple_vma_open(vma);
94return0;
95}

这里我们要介绍一下,当用户空间调用mmap执行内存映射时,file_operations结构的mmap函数被调用,其函数原型是:

[cpp]viewplaincopyint(*mmap)(structfile*filp,structvm_area_struct*vma);

其中vma包含了用于访问设备的虚拟地址信息。vma中vm_area_struct结构体类型,该结构体用于描述一个虚拟内存区,在2.6.10上,其定义如下:

[cpp]viewplaincopy55/*
56*ThisstructdefinesamemoryVMMmemoryarea.Thereisoneofthese
57*perVM-area/task.AVMareaisanypartoftheprocessvirtualmemory
58*spacethathasaspecialruleforthepage-faulthandlers(ieashared
59*library,theexecutableareaetc).
60*/
61structvm_area_struct{
62structmm_struct*vm_mm;/*Theaddressspacewebelongto.*/
63unsignedlongvm_start;/*Ourstartaddresswithinvm_mm.*/
64unsignedlongvm_end;/*Thefirstbyteafterourendaddress
65withinvm_mm.*/
66
67/*linkedlistofVMareaspertask,sortedbyaddress*/
68structvm_area_struct*vm_next;
69
70pgprot_tvm_page_prot;/*AccesspermissionsofthisVMA.*/
71unsignedlongvm_flags;/*Flags,listedbelow.*/
72
73structrb_nodevm_rb;
74
75/*
76*Forareaswithanaddressspaceandbackingstore,
77*linkageintotheaddress_space->i_mmappriotree,or
78*linkagetothelistoflikevmashangingoffitsnode,or
79*linkageofvmaintheaddress_space->i_mmap_nonlinearlist.
80*/
81union{
82struct{
83structlist_headlist;
84void*parent;/*alignswithprio_tree_nodeparent*/
85structvm_area_struct*head;
86}vm_set;
87
88structprio_tree_nodeprio_tree_node;
89}shared;
90
91/*
92*Afile'sMAP_PRIVATEvmacanbeinbothi_mmaptreeandanon_vma
93*list,afteraCOWofoneofthefilepages.AMAP_SHAREDvma
94*canonlybeinthei_mmaptree.AnanonymousMAP_PRIVATE,stack
95*orbrkvma(withNULLfile)canonlybeinananon_vmalist.
96*/
97structlist_headanon_vma_node;/*Serializedbyanon_vma->lock*/
98structanon_vma*anon_vma;/*Serializedbypage_table_lock*/
99
100/*Functionpointerstodealwiththisstruct.*/
101structvm_operations_struct*vm_ops;
102
103/*Informationaboutourbackingstore:*/
104unsignedlongvm_pgoff;/*Offset(withinvm_file)inPAGE_SIZE
105units,*not*PAGE_CACHE_SIZE*/
106structfile*vm_file;/*Filewemapto(canbeNULL).*/
107void*vm_private_data;/*wasvm_pte(sharedmem)*/
108
109#ifdefCONFIG_NUMA
110structmempolicy*vm_policy;/*NUMApolicyfortheVMA*/
111#endif
112};

vm_area_struct结构体描述的虚拟内存区介于vm_start和vm_end之间,vm_ops成员指向这个VMA的操作函数集,其类型为vm_operations_struct结构体,定义如下:

[cpp]viewplaincopy170/*
171*ThesearethevirtualMMfunctions-openingofanarea,closingand
172*unmappingit(neededtokeepfilesondiskup-to-dateetc),pointer
173*tothefunctionscalledwhenano-pageorawp-pageexceptionoccurs.
174*/
175structvm_operations_struct{
176void(*open)(structvm_area_struct*area);
177void(*close)(structvm_area_struct*area);
178structpage*(*nopage)(structvm_area_struct*area,unsignedlongaddress,int*type);
179int(*populate)(structvm_area_struct*area,unsignedlongaddress,unsignedlonglen,pgprot_tprot,unsignedlongpgoff,intnonblock);
180#ifdefCONFIG_NUMA
181int(*set_policy)(structvm_area_struct*vma,structmempolicy*new);
182structmempolicy*(*get_policy)(structvm_area_struct*vma,
183unsignedlongaddr);
184#endif
185};

当用户调用mmap系统调用时,内核会进行如下处理:

1.在进程的虚拟空间查找一块VMA.

2.将这块VMA进行映射.

3.如果设备驱动程序中定义了mmap函数,则调用它.

4.将这个VMA插入到进程的VMA链表中.

内存映射工作大部分由内核完成,驱动程序中的mmap函数只需要为该地址范围建立合适的页表,并将vma->vm_ops替换为一系列的新操作就可以了。有两种建立页表的方法,一是使用remap_pfn_range函数一次全部建立,或者通过nopage方法每次建立一个页表。

simple_remap_mmap函数使用remap_pfn_range函数一次建立全部页表,remap_pfn_range函数原型如下:

[cpp]viewplaincopyintremap_pfn_range(structvm_area_struct*vma,unsignedlongvirt_addr,unsignedlongpfn,unsignedlongsize,pgprot_tprot);

vma代表虚拟内存区域。

virt_addr代表要建立页表的用户虚拟地址的起始地址,remap_pfn_range函数为处于virt_addr和virt_addr+size之间的虚拟地址建立页表。

pfn是与物理内存起始地址对应的页帧号,虚拟内存将要被映射到该物理内存上。页帧号只是将物理地址右移PAGE_SHIFT位。在大多数情况下,VMA结构中的vm_pgoff赋值给pfn即可。remap_pfn_range函数建立页表,对应的物理地址是pfn<<PAGE_SHIFT到pfn<<(PAGE_SHIFT)+size。

size代表虚拟内存区域大小。

port是VMA要求的protection属性,驱动程序只要使用vma->vm_page_prot中的值即可。

在simple_remap_mmap函数中,

87-90行,调用remap_pfn_range函数建立页表:

[cpp]viewplaincopyremap_pfn_range(vma,vma->vm_start,vma->vm_pgoff,
vma->vm_end-vma->vm_start,
vma->vm_page_prot)

可对比上面对remap_pfn_range函数的参数的解释来理解。

92行,vma->vm_ops是structvm_operations_struct类型变量,代表内核操作虚拟内存区的函数集,这里赋值为simple_remap_vm_ops,其定义如下:

[cpp]viewplaincopy80staticstructvm_operations_structsimple_remap_vm_ops={
81.open=simple_vma_open,
82.close=simple_vma_close,
83};

这里仅仅实现了open和close函数,其它函数由内核提供。当进程打开或关闭VMA时,就会调用这两个函数,当fork进程或者创建一个新的对VMA引用时,也会调用open函数。实际的打开和关闭工作由内核完成,这里实现的open和close函数不必重复内核所做的工作,只要根据驱动程序的需要处理其他必要的事情。对于simple这样的简单驱动程序,simple_vma_open和simple_vma_close函数仅仅是打印相关信息:

[cpp]viewplaincopy63voidsimple_vma_open(structvm_area_struct*vma)
64{
65printk(KERN_NOTICE"SimpleVMAopen,virt%lx,phys%lx\n",
66vma->vm_start,vma->vm_pgoff<<PAGE_SHIFT);
67}
68
69voidsimple_vma_close(structvm_area_struct*vma)
70{
71printk(KERN_NOTICE"SimpleVMAclose.\n");
72}

回到simple_remap_mmap函数:

93行,显式调用simple_vma_open(vma),这里要注意,必须显示调用该函数,因为open函数还没有注册到系统。

至此,Device0的相关代码就分析完了。

除了remap_pfn_range函数外,在驱动程序中实现nopage函数通常可以为设备提供更加灵活的内存映射途径。当访问的页面不在内存,即发生缺页中断时,nopage就会被调用。这是因为,当发生缺页中断时,系统会经过如下处理过程:

1.找到缺页的虚拟地址所在的VMA。

2.如果必要,分配中间页目录表和页表。

3.如果页表项对应的物理页面不存在,则调用nopage函数,它返回物理页面的页描述符。

4.将物理页面的地址填充到页表中。

下面我们看Device1的相关代码,其文件操作函数集如下:

[cpp]viewplaincopy174/*Device1usesnopage*/
175staticstructfile_operationssimple_nopage_ops={
176.owner=THIS_MODULE,
177.open=simple_open,
178.release=simple_release,
179.mmap=simple_nopage_mmap,
180};

与Device0相比,只有mmap的实现不一样,我们看simple_nopage_mmap:

[cpp]viewplaincopy131staticintsimple_nopage_mmap(structfile*filp,structvm_area_struct*vma)
132{
133unsignedlongoffset=vma->vm_pgoff<<PAGE_SHIFT;
134
135if(offset>=__pa(high_memory)||(filp->f_flags&O_SYNC))
136vma->vm_flags|=VM_IO;
137vma->vm_flags|=VM_RESERVED;
138
139vma->vm_ops=&simple_nopage_vm_ops;
140simple_vma_open(vma);
141return0;
142}

135-137行,设置vma->vm_flags标志。

139行,指定vma->vm_ops为simple_nopage_vm_ops。

140行,显式调用simple_vma_open函数。

simple_nopage_vm_ops结构定义如下:

[cpp]viewplaincopy125staticstructvm_operations_structsimple_nopage_vm_ops={
126.open=simple_vma_open,
127.close=simple_vma_close,
128.nopage=simple_vma_nopage,
129};

这个结构体中,需要分析的是simple_vma_nopage函数:

[cpp]viewplaincopy102structpage*simple_vma_nopage(structvm_area_struct*vma,
103unsignedlongaddress,int*type)
104{
105structpage*pageptr;
106unsignedlongoffset=vma->vm_pgoff<<PAGE_SHIFT;
107unsignedlongphysaddr=address-vma->vm_start+offset;
108unsignedlongpageframe=physaddr>>PAGE_SHIFT;
109
110//Eventuallyremovetheseprintks
111printk(KERN_NOTICE"----Nopage,off%lxphys%lx\n",offset,physaddr);
112printk(KERN_NOTICE"VAis%p\n",__va(physaddr));
113printk(KERN_NOTICE"Pageat%p\n",virt_to_page(__va(physaddr)));
114if(!pfn_valid(pageframe))
115returnNOPAGE_SIGBUS;
116pageptr=pfn_to_page(pageframe);
117printk(KERN_NOTICE"page->index=%ldmapping%p\n",pageptr->index,pageptr->mapping);
118printk(KERN_NOTICE"Pageframe%ld\n",pageframe);
119get_page(pageptr);
120if(type)
121*type=VM_FAULT_MINOR;
122returnpageptr;
123}

106行,得到起始物理地址保存在offset中。

107行,得到address参数对应的物理地址,保存在physaddr中。

108行,得到address的物理地址对应的页帧号,保存在pageframe中。

116行,使用pfn_to_page函数,由页帧号返回对应的page结构指针保存在pageptr中。

119行,调用get_page增加pageptr指向页面的引用计数。

至此,simple模块的代码我们就分析完了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: