2013年8月9日 星期五

[轉] android pmem 和ashmem 介紹及實例分析

1Ashmem(匿名共享內存驅動:Anonymous Shared Memory)

它基於mmap系統調用,不同進程可以將同一段物理內存映射到各自的虛擬地址控制,從而實現共享

A(mmap:是一種共享內存的系統。假如:A進程的內存空間範圍0X00000XFFFFB進程的內存空間範圍0X0000~0XFFFF,他們兩個進程想共同共享一個文件或一段空間時,可以使用mmap(比如都想讀取硬盤上的c.txttxt內容為"123"),首先另外開闢第三個內存空間(3個字節),將硬盤上的c.txt映射到這個內存空間中,使此內存空間有了這個c.txt,再將AB進程分別映射至這個內存空間,則現在A進程的內核空間範圍為0X00000XFFFF+4B進程的內核空間範圍為0X00000XFFFF+4。那麼此時AB進程都擁有了共同的內存空間,即可以互相共享共同內存空間裡的內容了;當然,如果創建mmap時也可以指定是可讀還是可寫,如果AB改變了共同內存空間的值,將c.txt內容改為了"234"的話,硬盤上的c.txt內容仍然為123,若想改變,則得調用msync實現硬盤和共享內存區的同步),而Ashmemmmap稍有不同的是,Ashmemcache shrinker關聯起來,可以在適當時機去回收這些共享內存,這點比較智能,而mmap是做不到的

BAshmem實現

Ashmem類位於/android2.1/kernel/mm/ashmem.c,通過註冊cache shrinker來實現回收內存,通過註冊misc提供mmap接口等。Ashmem用兩個結構體ashmem_areaashmem_range來維護,ashmem_area代表共享內存的區域,ashmem_range則將這段區域以頁為單位分為多個rangeashmem_area有個unpinned_list成員,掛在這個list上的range可以被回收。ashmem_range有一個LRU鏈表,在cache shrink回收一個ashmem_area的某段內存時候,是根據LRU的原則來選擇哪些頁面優先被回收的

CAshmem流程

ashmem_init(創建struct ashmem_areastruct ashmem_range、註冊ashmem driver(misc_register)、註冊cache shrinker)----->在註冊misc構造方法時,引進了ashmem_fops----->在註冊fops時,創建了ashmem_openashmem_releaseashmem_mmap

ashmem_shrinkashmem_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_areasize啊,然後查看被pinrange有多少啊,然後pin or unpin range(pin range代表此rangeunpinned_list中取下來,而unpin range代表此range掛在unpinned_list上,以便被回收,由此可見只有被unpinrange才會被回收)

D、用戶接口

進程A可通過open打開該文件,用ioctl命令ASHMEM_SET_NAMEASHMEM_SET_SIZE設置共享內存塊的名字和大小,並將得到的handle傳給mmap,來獲得共享的內存區域,進程B通過將相同的handle傳給mmap,獲得同一塊內存,handle在進程間的傳遞可通過Binder來實現。


2Android PMEM
pmemashmem都通過mmap實現共享,區別是Pmem的共享區域是一段連續的物理內存,而Ashmem的共享區域在虛擬空間是連續的,物理內存卻不一定連續
APMEM的實現
Pmem的源代碼在drivers/misc/pmem.c中,Pmem驅動依賴於linuxmisc deviceplatform driver框架,一個系統可以有多個Pmem,默認的是最多10個。Pmem暴露4組操作,分別是platform driverproberemove操作; misc devicefops接口和vm_ops操作。模塊初始化時會註冊一個platform driver,在之後probe時,創建misc設備文件,分配內存,完成初始化工作。
Pmem通過pmem_infopmem_datapmem_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_datamaster進程的pmem_data建立連接關係,這個進程就成為client進程。Client進程可以通過mmapmaster Pmem中的一段或全部重新映射到自己的進程空間,這樣就實現了共享Pmem內存。如果是GPUDSP則可以通過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, &region) >= 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並不像Ashmembinder那樣,選中就可以被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目錄,多了pmempmem_adsp
  • 沒有留言:

    張貼留言