1、Ashmem(匿名共享內存驅動:AnonymousShared Memory)
它基於mmap系統調用,不同進程可以將同一段物理內存映射到各自的虛擬地址控制,從而實現共享
A、(mmap:是一種共享內存的系統。假如:A進程的內存空間範圍0X0000~0XFFFF,B進程的內存空間範圍0X0000~0XFFFF,他們兩個進程想共同共享一個文件或一段空間時,可以使用mmap(比如都想讀取硬盤上的c.txt,txt內容爲"123"),首先另外開闢第三個內存空間(3個字節),將硬盤上的c.txt映射到這個內存空間中,使此內存空間有了這個c.txt,再將A、B進程分別映射至這個內存空間,則現在A進程的內核空間範圍爲0X0000~0XFFFF+4,B進程的內核空間範圍爲0X0000~0XFFFF+4。那麼此時A、B進程都擁有了共同的內存空間,即可以互相共享共同內存空間裏的內容了;當然,如果創建mmap時也可以指定是可讀還是可寫,如果A或B改變了共同內存空間的值,將c.txt內容改爲了"234"的話,硬盤上的c.txt內容仍然爲123,若想改變,則得調用msync實現硬盤和共享內存區的同步),而Ashmem與mmap稍有不同的是,Ashmem與cacheshrinker關聯起來,可以在適當時機去回收這些共享內存,這點比較智能,而mmap是做不到的
B、Ashmem實現
Ashmem類位於/android2.1/kernel/mm/ashmem.c,通過註冊cacheshrinker來實現回收內存,通過註冊misc提供mmap接口等。Ashmem用兩個結構體ashmem_area和ashmem_range來維護,ashmem_area代表共享內存的區域,ashmem_range則將這段區域以頁爲單位分爲多個range。ashmem_area有個unpinned_list成員,掛在這個list上的range可以被回收。ashmem_range有一個LRU鏈表,在cacheshrink回收一個ashmem_area的某段內存時候,是根據LRU的原則來選擇哪些頁面優先被回收的
C、Ashmem流程
ashmem_init(創建structashmem_area和structashmem_range、註冊ashmemdriver(misc_register)、註冊cacheshrinker)----->在註冊misc構造方法時,引進了ashmem_fops----->在註冊fops時,創建了ashmem_open、ashmem_release、ashmem_mmap、
ashmem_shrink、ashmem_ioctl---->創建ashmem_open時,調用了kmem_cache_zalloc去分配了一個ashmem_area並初始化了成員變量、創建ashmem_release,調用了了kmem_cache_free,此靜態方法與zalloc相反,是去釋放ashmem_area、創建ashmem_mmap調用shmem_file_setup來從tmpfs系統(基於內存的文件系統)中創建一個文件(內存)給ashmem_area用,這個內存就是共享內存、創建ashmem_shrink來實現內存回收,這個函數從LRU鏈表上回收指定數目的unpinnedashmem_range、創建ashmem_ioctl,設置一些ashmem_area的size啊,然後查看被pin的range有多少啊,然後pinor unpin range(pin range代表此range從unpinned_list中取下來,而unpinrange代表此range掛在unpinned_list上,以便被回收,由此可見只有被unpin的range纔會被回收)
D、用戶接口
進程A可通過open打開該文件,用ioctl命令ASHMEM_SET_NAME和ASHMEM_SET_SIZE設置共享內存塊的名字和大小,並將得到的handle傳給mmap,來獲得共享的內存區域,進程B通過將相同的handle傳給mmap,獲得同一塊內存,handle在進程間的傳遞可通過Binder來實現。
2、AndroidPMEM
pmem與ashmem都通過mmap實現共享,區別是Pmem的共享區域是一段連續的物理內存,而Ashmem的共享區域在虛擬空間是連續的,物理內存卻不一定連續
A、PMEM的實現
Pmem的源代碼在drivers/misc/pmem.c中,Pmem驅動依賴於linux的miscdevice和platformdriver框架,一個系統可以有多個Pmem,默認的是最多10個。Pmem暴露4組操作,分別是platformdriver的probe和remove操作;miscdevice的fops接口和vm_ops操作。模塊初始化時會註冊一個platformdriver,在之後probe時,創建misc設備文件,分配內存,完成初始化工作。
Pmem通過pmem_info,pmem_data,pmem_region三個結構體維護分配的共享內存,其中pmem_info代表一個Pmem設備分配的內存塊,pmem_data代表該內存塊的一個子塊,pmem_region則把每個子塊分成多個區域。pmem_data是分配的基本單位,即每次應用層要分配一塊Pmem內存,就會有一個pmem_data來表示這個被分配的內存塊,實際上在open的時候,並不是open一個pmem_info表示的整個Pmem內存塊,而是創建一個pmem_data以備使用。一個應用可以通過ioctl來分配pmem_data中的一個區域,並可以把它map到進程空間;並不一定每次都要分配和map整個pmem_data內存塊
B、用戶接口
一個進程首先打開Pmem設備,通過ioctl(PMEM_ALLOCATE)分配內存,它mmap這段內存到自己的進程空間後,該進程成爲master進程。其他進程可以重新打開這個pmem設
備,通過調用ioctl(PMEM_CONNECT)將自己的pmem_data與master進程的pmem_data建立連接關係,這個進程就成爲client進程。Client進程可以通過mmap將masterPmem中的一段或全部重新映射到自己的進程空間,這樣就實現了共享Pmem內存。如果是GPU或DSP則可以通過ioctl(PMEM_GET_PHYS)獲取物理地址進行操作。
3.在應用程序中的使用:
1)
Pmem例子:
sp<MemoryHeapBase>master_workspace = new MemoryHeapBase(pmem_adsp,Coda_WorkSpace_Size);
//new一個base
memeoryHeapBase,構造函數中做了2件事情,一個是open“/dev/pmem_adsp”設備,第二是調用mapfd(內部即mmap)來得到共享內存區域
if(master_workspace->heapID() < 0) {
LOGD("Errorcreating workspace heap");
Status= UNKNOWN_ERROR;
}
master_workspace->setDevice(pmem);//如果pmem_adspdevice出錯,就是用pmemdevice
mHeapPmem_workspace= new MemoryHeapPmem(master_workspace, 0);
//new一個Pmem, MemoryHeapPmem ,(構造函數中調用init,設定了Base,device,size等)
mHeapPmem_workspace->slap();
master_workspace.clear();
if(ioctl(mHeapPmem_workspace->heapID(), PMEM_GET_PHYS, ®ion)>= 0)//得到物理地址
{
pys_address= (unsigned int)region.offset;
WORK_SPACE_PMEM_PHY_ADDRESS= pys_address;
WORK_SPACE_PMEM_VIR_ADDRESS= mHeapPmem_workspace->base(); //得到虛擬地址
}
else
{
pys_address= 0xFFFFFFFF;
LOGE("Error:WORKSPACE PMEM_GET_PHYS FAILED");
returnUNKNOWN_ERROR;
}
2)
Ashmem例子:
sp<MemoryHeapBase>heap = new MemoryHeapBase(frameSize * kBufferCount);
if(heap->heapID() < 0) {
LOGE("Errorcreating frame buffer heap");
returnfalse;
}
//沒有指定device,就是用的ashmem.構造函數中做了2件事情,一個是ashmem_create_region,第二是調用mapfd(內部即mmap)來得到共享內存區域
第一步通過調用ashmem_create_region函數,這個函數完成這幾件事:
1)fd= open("/dev/ashmem", O_RDWR);
2)ioctl(fd,ASHMEM_SET_NAME, region_name); // 這一步可選
3)ioctl(fd,ASHMEM_SET_SIZE, region_size);
第二步,應用程序一般會調用mmap來把ashmem分配的空間映射到進程空間:
mapAddr= mmap(NULL, pHdr->mapLength, PROT_READ | PROT_WRITE, MAP_PRIVATE,fd, 0);
4.如何將PMEM編入內核
AndroidPMEM驅動研究(1)——如何將PMEM編入內核
PMEM並不像Ashmem和binder那樣,選中就可以被Android系統使用,他是一個platform設備,需要註冊纔可以使用。
下面以S3C6410爲例,描述使用流程:
1)選中內核選項
DeviceDrivers --->
Miscdevices --->
Androidpmem allocator
2)修改你的dev.c註冊文件,添加如下內容:
#ifdefCONFIG_ANDROID_PMEM
staticstruct android_pmem_platform_data android_pmem_pdata = {
.name= "pmem",
.start= PMEM_BASE,
.size= PMEM_BASE_SIZE,
.no_allocator = 1,
.cached= 1,
};
staticstruct android_pmem_platform_data android_pmem_adsp_pdata = {
.name= "pmem_adsp",
.start= PMEM_ADSP_BASE,
.size= PMEM_ADSP_BASE_SIZE,
.no_allocator = 0,
.cached= 0,
};
structplatform_device android_pmem_device = {
.name= "android_pmem",
.id= 0,
.dev= { .platform_data = &android_pmem_pdata },
};
structplatform_device android_pmem_adsp_device = {
.name= "android_pmem",
.id= 1,
.dev= { .platform_data = &android_pmem_adsp_pdata },
};
#endif
3)在驅動註冊列表中添加如下內容:
staticstruct platform_device *smdk6410_devices[] __initdata = {
#ifdefCONFIG_ANDROID_PMEM
&android_pmem_device,
&android_pmem_adsp_device,
#endif
};
4)分配物理地址我用了128MB的最後8MB
#definePMEM_BASE 0x57900000
#definePMEM_BASE_SIZE SZ_1M*4
#definePMEM_ADSP_BASE 0x57c00000
#definePMEM_ADSP_BASE_SIZE SZ_1M*4
5)重新編譯內核
6)修改bootargs減少Linux可管理的MEM
MEM=120MB
7)重新啓動系統
啓動信息:
pmem:1 init
pmem_adsp:0 init
8)查看dev目錄,多了pmem和pmem_adsp