对PMEM机制的实现的分析同样可以从该设备的初始化开始,进而分析整个机制的运作。在pmem.c文件中我们可以发现,该设备通过module_init和module_exit分别定义了其初始化和退出函数,实现如下:
static int pmem_probe(structplatform_device *pdev)
{
struct android_pmem_platform_data*pdata;
if (!pdev ||!pdev->dev.platform_data) {
printk(KERN_ALERT “Unable to probepmem!\n”);
return -1;
}
pdata = pdev->dev.platform_data;
return pmem_setup(pdata, NULL, NULL);
}
static int pmem_remove(structplatform_device *pdev)
{
int id = pdev->id;
__free_page(pfn_to_page(pmem[id].garbage_pfn));
misc_deregister(&pmem[id].dev);
return 0;
}
static struct platform_driverpmem_driver = {
.probe = pmem_probe,
.remove = pmem_remove,
.driver = {.name = “android_pmem”}
};
static int __init pmem_init(void)
{
returnplatform_driver_register(&pmem_driver);
}
static void __exit pmem_exit(void)
{
platform_driver_unregister(&pmem_driver);
}
module_init(pmem_init);
module_exit(pmem_exit);
当系统启动时,就会进入pmem_init函数,该函数会调用platform_driver_register来注册一个platform驱动。pmem_driver中指明了该驱动的名称、probe以及remove,其中remove用于退出时通过pmem_exit->platform_driver_unregister->pmem_remove来执行退出操作。退出过程很简单,这里我们继续来分析初始化操作。在注册了platform驱动之后,就会执行probe所指明的pmem_probe函数,在该函数中通过pmem_setup来完成初始化操作。pmem_setup的实现如下所示:
ini pmem_setup(structandroid_pmem_platform_data pdata,long (ioctl)(struct file , unsigned int, unsigned long), int(*release) (struct inode *, struct file *))
{
interr = 0;
intI, index = 0;
intid = id_count;
id_count++;
//初始化pmem_info结构体
pmem[id].no_allocator= pdata->no_allocator;
pmem[id].cached= pdata->cached;
pmem[id].buffered= pdata->buffered;
pmem[id].base= pdata->start;
pmem[id].size= pdata->size;
pmem[id].ioctl= ioctl;
pmem[id].release= release;
init_rwsem(&pmem[id].bitmap_sem);
init_MUTEX(&pmem[id].data_list_sem);
INIT_LIST_HEAD(&pmem[id].data_list);
pmem[id].dev.name= pdata->name;
pmem[id].dev.minor= id;
pmem[id].dev.fops= &pmem_fops;
printk(KERN_INF“%s: %d init\n”, pdata->name, pdata->cached);
//注册miscdevice
err= misc_register(&pmem[id].dev);
if(err) {
printk(KERN_ALERT“Unable to register pmem driver!\n”);
gotoerr_cant_register_device;
}
pmem[id].num_entries= pmem[id].size / PMEM_MIN_ALLOC;
pmem[id].bitmap= kmalloc(pmem[id].num_entries * sizeof(struct pmem_bits),GFP_KERNEL);
if(!pmem[id].bitmap)
gotoerror_no_mem_for_metadata;
memset(pmem[id].bitmap,0, sizeof(struct pmem_bits) * pmem[id].num_entries);
for(I = sizeof(pmem[id].num_entries) * 8 -1; i>=0; i--) {
if((pmem[id].num_entries) & 1<<i) {
PMEM_ORDER(id,index) = I;
index= PMEM_NEXT_INDEX(id, index);
}
}
if(pmem[id].cached)
pmem[id].vbase= ioremap_cached(pmem[id].base, pmem[id].size);
#ifdefioremap_ext_buffered
elseif (pmem[id].buffered)
pmem[id].vbase= ioremap_ext_buffered(pmem[id].base, pmem[id].size);
#endif
else
pmem[id].vbase= ioremap(pmem[id].base, pmem[id].size);
if(pmem[id].vbase == 0)
gotoerror_cant_remap;
pmem[id].garbage_pfn= page_to_pfn(alloc_page(GFP_KERNEL));
if(pmem[id].no_allocator)
pmem[id].allocated= 0;
#ifPMEM_DEBUG
//Debug操作
debugfs_create_file(pdata->name,S_IFREG | S_IRUGO, NULL, (void *)id, &debug_fops);
#endif
return0;
error_cant_remap:
kfree(pmem[id].bitmap);
error_no_mem_for_metadata:
misc_deregister(&pmem[id].dev);
error_cant_register_device:
return-1;
}
该函数首先会初始化一个pmem_info结构体来代表PMEM所管理的连续内存,然后注册miscdevice;在注册miscdevice的同时也指明了file_operations操作,使得PMEM也具有file_operations的常用操作。下面是其具体操作:
structfile_operations pmem_fops = {
.release= pmem_release,
.mmap= pmem_mmap,
.open= pmem_open,
.unlocked_ioctl= pmem_ioctl,
};
以上包括了常用的打开、释放、mmap和ioctl等操作,后面我们将会对这些操作进行具体分析。
在初始化过程中还检测了PMEM_DEBUG,它表示如果定义了PMEM_DEBUG,就在debugfs中创建一个用于调试的文件,并指定调试操作debugfops,调试操作定义如下:
staticstruct file_operations debug_fops = {
.read= debug_read,
.open= debug_open,
};
其中主要包括open(打开)和read(读取)的方法,用于打开和读取调试文件。
到这里,整个初始化过程就完成了。下面我们来分析这些具体的操作方法的实现。首先看打开操作pmem_open,其实现如下:
staticint pmem_open(struct inode *inode, struct file *file)
{
structpmem_data *data;
intid = get_id(file);
intret = 0;
DLOG(“current%u file %p(%d)\n”, current->pid, file, file_count(file));
//重复判断
if(file->private_data!= NULL)
return-1;
//分配pmem_data空间
data= kmalloc(sizeof(struct pmem_data), GFP_KERNEL);
if(!data) {
printk(“pmem:unable to allocate memory for pmem metadata.”);
retrun– 1;
}
//初始化pmem_data
data->flags= 0;
data->index= -1;
data->task= NULL;
data->vma= NULL;
data->pid= 0;
data->master_file= NULL;
#ifPMEM_DEBUG
data->ref= 0;
#endif
INIT_LIST_HEAD(&data->region_list);
init_rwsem(&data->sem);
//将数据保存到private_data中
file->private_data= data;
INIT_LIST_HEAD(&data->list);
down(&pmem[id].data_list_sem);
list_add(&data->list,&pmem[id].data_list);
up(&pmem[id].data_list_sem);
returnret;
}
该函数被应用层调用,用来打开设备。进入函数后,首先需要判断PMEM设备是否已经被打开,以避免重复打开;因为按规定,一个进程只能打开同一个设备一次。接下来就是分配pmem_data的存储空间,并进行初始化操作,表示在本次打开操作过程中从PMEM中获取的一块内存。需要注意的是,这里实际上并没有真的分配内存,只是初始化了一个pmem_data结构体,真正的分配操作是通过pmem_allocate函数来实现。同样,如果定义了PMEM_DEBUG,则需要将pmem_data->ref设备为0。最后,将分配到的pmem_data数据保存到private_data中,完成打开操作。
现在我们看一下PMEM是如何分配内存的,pmem_allocate函数的定义如下:
staticint pmem_allocate(int id, unsigned long len)
{
intcurr = 0;
intend = pmem[id].num_entries;
intbest_fit = -1;
unsignedlong order = pmem_order(len);
//no_allocator模式下直接使用整块内存
if(pmem[id].no_allocator) {
DLOG(“noallocator”);
if((len > pmem[id].size) || pmem[id].allocated)
return– 1;
pmem[id].allocated= 1;
returnlen;
}
if(order > PMEM_MAX_ORDER)
return-1;
DLOG(“order%1x\n”, order);
//寻找最合适的内存块
while(curr< end) {
if(PMEM_IS_FREE(id, curr)) {
if(PMEM_ORDER(id, curr) == (unsigned char) order) {
best_fit= curr;
break;
}
if(PMEM_ORDER(id, curr) > (unsigned char)order && (best_fit< 0 || PMEM_ORDER(id, curr) < PMEM_ORDER(id, best_fit)))
best_fit= curr;
}
curr= PMEM_NEXT_INDEX(id, curr);
}
//如果没有找到最合适的内存块,则分配失败。
If(best_fit < 0) {
printk(“pmem:no space left to allocate !\n”);
return-1;
}
//分配指定的最合适的内存块(best_fit)
while(PMEM_ORDER(id, best_fit) > (unsigned char) order) {
intbuddy;
PMEM_ORDER(id,best_fit) -= 1;
buddy= PMEM_BUDDY_INDEX(id, best_fit);
PMEM_ORDER(id,buddy) = PMEM_ORDER(id, best_fit);
}
//该内存块已经被分配
pmem[id].bitmap[best_fit].allocated= 1;
returnbest_fit;
}
该函数首先会判断分配的模式是no_allocator还是allocator;如果是no_allocator,则直接将整块内存作为分配的内存块,否则寻找一块最合适的内存(best_fit)进行分配。分配完成之后将bitmap标志设置为1,表示该块内存已经被分配。
注意:该函数并不是在pmem_open中调用的,而是在mmap函数中调用的,当然也可以通过ioctl函数来调用。
下面我们就来分析mmap函数的实现。
pmem_mmap函数定义如下:
staticint pmem_mmap(struct file *file, struct vm_area_struct *vma)
{
structpmem_data *data;
intindex;
unsignedlong vma_size = vma->vm_end – vma->vm_start;
intret = 0, id = get_id(file);
//判断是否被map
if(vma->vm_pgoff || !PMEM_IS_PAGE_ALGNED(vma_size)) {
#ifPMEM_DEBUG
printk(KERN_ERR“pmem: mmaps must be at offset zero, aligned”
“and a multiple of pages_size.\n”);
#endif
return-EINVAL;
}
data= (struct pmem_data *)file->private_data;
down_write(&data->sem);
/*check this file isn't already mmaped, for submaps check this file
*has never been mmaped */
if((data->flags & PMEM_FLAGS_MASTERMAP) ||
(data->flags & PMEM_FLAGS_SUBMAP) ||
(data->flags & PMEM_FLAGS_UNSUBMAP)) {
#ifPMEM_DEBUG
printk(KERN_ERR“pmem: you can only mmap a pmem file once, “
“this file is already mmaped. %x\n”, data->flags);
#endif
ret= -EINVAL;
gotoerror;
}
//判断该内存块是否被分配
if(data && data->index == -1) {
down_write(&pmem[id].bitmap_sem);
index= pmem_allocate(id, vma->vm_end – vma->vm_start);
up_write(&pmem[id].bitmap_sem);
data->index= index;
}
if(!has_allocation(file)) {
ret= -EINVAL;
printk(“pmem:could not find allocation for map. \n”);
gotoerror;
}
if(pmem_len(id, data) < wma_size) {
#ifPMEM_DEBUG
printk(KERN_WARNING“pmem: mmap size [%1u] does not match “
“sizeof backing region [%1u].\n”, vma_size,
pmem_len(id,data));
#endif
ret= -EINVAL;
gotoerror;
}
vma->vm_pgoff= pmem_start_add(id, data) >> PAGE_SHIFT;
vma->vm_page_prot= phys_mem_access_prot(file, vma->vm_page_port);
//判断该内存块是否已经被连接
if(data->flags & PMEM_FLAGS_CONNECTED) {
structpmem_region_node *region_node;
structlist_head *elt;
if(pmem_map_garbage(id, vma, data, 0, vma_size)) {
printk(“pmem:mmap failed in kernel!\n”);
ret= -EAGAIN;
gotoerror;
}
//对每个region进行映射
list_for_each(elt,&data->region_list) {
region_node= list_entry(elt, struct pmem_region_node, list);
DLOG(“remappingfile: %p %lx %lx\n”, file,
region_node->region.offset,
region_node->region.len);
if(pmem_remap_pfn_range(id, vma, data, region_node->region.offset,
region_node->region.len)){
ret= -EAGAIN;
gotoerror;
}
}
//改变标志
data->flags|= PMEM_FLAGS_SUBMAP;
get_task_struct(current->group_leader);
data->task= current->group_leader;
data->vma= vma;
#ifPMEM_DEBUG
//调试,取得当前进程的pid
data->pid= current->pid;
#endif
DLOG(“submmappedfile %p vma %p pid %u\n”, file, vma, current->pid);
}else { //如果没有连接,则对整个区域进行映射
if(pmem_map_pfn_range(id, vma, data, 0, vma_size)) {
printk(KERN_INFO“pmem: mmap failed in kernel!\n”);
ret= -EAGAIN;
gotoerror;
}
//改变标志
data->flags|= PMEM_FLAGS_MASTERMAP;
data->pid= current->pid;
}
//指明vm操作函数
vma->vm_ops= &vm_ops;
error:
up_write(&data->sem);
returnret;
}
该函数主要实现了mmap功能。进入函数后,首先判断该区域(pmem_data内存块)是否已经被map,如果是,则退出;如果该区域还未被分配,则调用pmem_allocate来分配。如果该区域已经被connect,则调用pmem_remap_pfn_range进行映射;否则,调用pmem_remap_pfn_range对整个区域进行map,并设置标志pmem_data->flags|= PMEM_FLAGS_MASTERMAP,表示该区域是一个master映射。最后,设置vm操作函数vm_ops。
所谓connect,就是实现两个进程共享一个PMEM内存,其中一个进程首先打开PMEM设备,它将得到一个pmem_data,再将这块内存映射到自己的进程空间。该进程被称为master映射,它所拥有的pmem_data就是所谓的master_file(这个map也被称为MASTERMAP)。其他进程可以重新打开这个PMEM设备,但是它获取的是另一个pmem_data,它通过调用pmem_connect使自己的pmem_data与master进程的pmem_data建立连接关系,这个进程就是所谓的client进程。client进程拥有的pmem_data被称为master进程拥有的PMEM的子块(suballocation)。client进程可以通过mmap或者ioctl调用pmem_remap,将masterpmem中的一段(也可以是全部)重新映射到自己的进程空间,这样就实现了PMEM内存的共享。
下面的出场的是pmem_remap,其定义如下:
intpmem_remap(struct pmem_region *region, struct file *file, unsignedoperation)
{
intret;
structpmem_region_node *region_node;
structmm_struct *mm = NULL;
structlist_head *elt, *elt2;
intid = get_id(file);
structpmem_data *data = (struct pmem_data *)file->private_data;
if(unlikely(!PMEM_IS_PAGE_ALIGNED(region->offset) ||
!PMEM_IS_PAGE_ALIGNED(region->len))){
return-EINVAL;
}
if(region->len == 0)
return0;
ret= pmem_lock_data_and_mm(file, data, &mm);
if(ret) return 0;
/*只有masterfile 的owner才能remap,如不是则返回*/
if(!is_master_owner(file)) {
ret= -EINVAL;
gotoerr;
}
//判断请求空间是否有效
if(unlikely((regionn->offset > pmem_len(id, data)) ||
(region->len> pmem_len(id, data)) ||
(region->offset+ region->len > pmem_len(id, data)))) {
ret= -EINVAL;
gotoerr;
}
//判断是PMEM_MAP,还是PMEM_UNMAP
if(operation == PMEM_MAP) {
//分配pmem_region_node并初始化
region_node= kmalloc(sizeof(struct pmem_region_node), GFP_KERNEL);
if(!region_node) {
ret= -ENOMEM;
gotoerr;
}
region_node->region = *region;
list_add(®ion_node->list,&data->region_list);
}else if (operation == PMEM_UNMAP) {
intfound = 0;
//遍历每个region
list_for_each_safe(elt,elt2, &data->region_list) {
region_node= list_entry(elt, struct pmem_region_node, list);
if(region->len == 0 ||
(region_node->region.offset== region->offset &&
region_node->region.len== region->len)) {
list_del(elt);
kfree(region_node);
found= 1;
}
}
if(!found) {
ret= -EINVAL;
gotoerr;
}
}
if(data->vma && PMEM_IS_SUBMAP(data)) {
if(operation == PMEM_MAP) // PMEM_MAP,则映射。
Ret= pmem_remap_pfn_range(id, data->vma, data, region->offset,region->len);
elseif (operation == PMEM_UNMAP) //UNMAP
ret= pmem_unmap_pfn_range(id, data->vma, data, region->offset,region->len);
}
err:
pmem_unlock_data_and_mm(data,mm);
returnret;
}
该函数用于完成对一块内存的remap操作。由于只有masterfile的owner才能remap,因此首先要判断当前进程是否是该pmem_data的masterfile的owner,然后判断请求remap的区域是否在有效范围,最后再根据operation参数决定是map还是unmap。如果是map,则分配一个pmem_region_node并初始化;如果是unmap,则把要unmap的region从pmem_data->region_list链表上缷下。最后,如果operation=PMEM_MAP,则调用pmem_remap_pfn_range完成映射;如果是operation=PMEM_UNMAP,则调用pmem_unmap_pfn_range完成unmap。
最后要分析的就是前面提到过的pmem_ioctl函数了,它主要用来完成一些常用操作,这些操作命令对应于android_pmem.h中定义的一些宏,如下所示:
- #definePMEM_GET_PHYS: 获取物理地址。
- #definePMEM_MAP pmem_remap(): 重映射一段内存。
- #definePMEM_GET_SIZE pmem_getsize(): 得到尺衬。
- #definePMEM_UNMAP pmem_remap(®ion, file, PMEM_UNMAP): unmap内存。
- #definePMEM_ALLOCATE: 分配PMEM空间,len是参数,如果文件已被分配失败。
- #define PMEM_CONNECT: 将需要被connected的文件链接到当前文件。相当于两个文件映射到同一块区域。
- #definePMEM_GET_TOTAL_SIZE: 返回PMEMregion的全部大小。
由于该函数只是一些常用操作的实现,并不影响我们对源码的分析,所以不再把对源码的具体实现贴出来。