Android PMEM驱动分析

来源:互联网 发布:3个数最小公倍数算法 编辑:程序博客网 时间:2024/04/30 00:52

对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(&region_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(&region, file, PMEM_UNMAP): unmap内存。

- #definePMEM_ALLOCATE: 分配PMEM空间,len是参数,如果文件已被分配失败。

- #define PMEM_CONNECT: 将需要被connected的文件链接到当前文件。相当于两个文件映射到同一块区域。

- #definePMEM_GET_TOTAL_SIZE: 返回PMEMregion的全部大小。

由于该函数只是一些常用操作的实现,并不影响我们对源码的分析,所以不再把对源码的具体实现贴出来。


原创粉丝点击