1、Ashmem(匿名共享內存驅動:Anonymous Shared 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與cache shrinker關聯起來,可以在適當時機去回收這些共享內存,這點比較智能,而mmap是做不到的
B、Ashmem實現
Ashmem類位於/android2.1/kernel/mm/ashmem.c,通過註冊cache shrinker來實現回收內存,通過註冊misc提供mmap接口等。Ashmem用兩個結構體ashmem_area和ashmem_range來維護,ashmem_area代表共享內存的區域,ashmem_range則將這段區域以頁為單位分為多個range。ashmem_area有個unpinned_list成員,掛在這個list上的range可以被回收。ashmem_range有一個LRU鏈表,在cache shrink回收一個ashmem_area的某段內存時候,是根據LRU的原則來選擇哪些頁面優先被回收的
C、Ashmem流程
ashmem_init(創建struct ashmem_area和struct ashmem_range、註冊ashmem driver(misc_register)、註冊cache shrinker)----->在註冊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鏈表上回收指定數目的unpinned ashmem_range、創建ashmem_ioctl,設置一些ashmem_area的size啊,然後查看被pin的range有多少啊,然後pin or unpin range(pin range代表此range從unpinned_list中取下來,而unpin range代表此range掛在unpinned_list上,以便被回收,由此可見只有被unpin的range才會被回收)
D、用戶接口
進程A可通過open打開該文件,用ioctl命令ASHMEM_SET_NAME和ASHMEM_SET_SIZE設置共享內存塊的名字和大小,並將得到的handle傳給mmap,來獲得共享的內存區域,進程B通過將相同的handle傳給mmap,獲得同一塊內存,handle在進程間的傳遞可通過Binder來實現。
2、Android PMEM
pmem與ashmem都通過mmap實現共享,區別是Pmem的共享區域是一段連續的物理內存,而Ashmem的共享區域在虛擬空間是連續的,物理內存卻不一定連續
A、PMEM的實現
Pmem的源代碼在drivers/misc/pmem.c中,Pmem驅動依賴於linux的misc device和platform driver框架,一個系統可以有多個Pmem,默認的是最多10個。Pmem暴露4組操作,分別是platform driver的probe和remove操作; misc device的fops接口和vm_ops操作。模塊初始化時會註冊一個platform driver,在之後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將master Pmem中的一段或全部重新映射到自己的進程空間,這樣就實現了共享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("Error creating workspace heap");
Status = UNKNOWN_ERROR;
}
master_workspace->setDevice(pmem); // 如果pmem_adsp device出錯, 就是用pmem device
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");
return UNKNOWN_ERROR;
}
2) Ashmem 例子:
sp<MemoryHeapBase> heap = new MemoryHeapBase(frameSize * kBufferCount);
if (heap->heapID() < 0) {
LOGE("Error creating frame buffer heap");
return false;
}
//沒有指定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編入內核
Android PMEM驅動研究(1)——如何將PMEM編入內核
PMEM並不像Ashmem和binder那樣,選中就可以被Android系統使用,他是一個platform設備,需要註冊才可以使用。
下面以S3C6410為例,描述使用流程:
1)選中內核選項 Device Drivers --->
Misc devices --->
Android pmem allocator
2)修改你的dev.c註冊文件,添加如下內容:
#ifdef CONFIG_ANDROID_PMEM static struct android_pmem_platform_data android_pmem_pdata = { .name = "pmem", .start = PMEM_BASE, .size = PMEM_BASE_SIZE, .no_allocator = 1, .cached = 1, };
static struct 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, };
struct platform_device android_pmem_device = { .name = "android_pmem", .id = 0, .dev = { .platform_data = &android_pmem_pdata }, };
struct platform_device android_pmem_adsp_device = { .name = "android_pmem", .id = 1, .dev = { .platform_data = &android_pmem_adsp_pdata }, }; #endif
3)在驅動註冊列表中添加如下內容: static struct platform_device *smdk6410_devices[] __initdata = { #ifdef CONFIG_ANDROID_PMEM &android_pmem_device, &android_pmem_adsp_device, #endif };
4)分配物理地址我用了128MB的最後8MB #define PMEM_BASE 0x57900000 #define PMEM_BASE_SIZE SZ_1M*4 #define PMEM_ADSP_BASE 0x57c00000 #define PMEM_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
|