在android原始碼的驅動目錄下,一般會有共享記憶體的相關實現原始碼,目錄是:kernel\drivers\staging\android\ashmem.c。但是本篇文章不是講解android共享記憶體的功能實現原理,而是講怎麼運用它。
1.
在linux中,不同程式間擁有自己獨立的記憶體空間,32位作業系統中好像一個程式能用的記憶體大小是4G吧。而且一般不同程式間不能夠互相使用各自記憶體的資料。
當然不同程式間共享資料方法很多,比如之前說的程式間通訊binder,socket等等,不過android出了一個共享記憶體的概念,為的是不同程式間能夠共同操作同一塊記憶體資料,比如程式1001往一塊共享記憶體addr裡面寫資料“hello world”,程式1009往這塊共享記憶體addr讀取出“hello world”,也能夠往這塊共享記憶體addr寫資料“hello china”。這就是共同享用了一塊記憶體的基本概念了(說白了就是同耕一塊田)。講的夠仔細了吧,如果不清楚評論區見。
注意:好像binder傳輸的資料實現也是類似於共享記憶體,讀者可以自行去了解。
2.
先說一下等會寫程式的思路:
首先想想程式碼編譯出兩個可執行檔案後如何操作,開啟兩個終端,都進入裝置adb shell,第一個終端執行程式a,第二個終端執行程式b。在程式a輸入一串資料後,在程式b中可以讀出這段資料(也能夠改寫這段資料,讀者可以自行新增這部分功能)。
然後再想想實現的方式,
程式a:1. 建立共享記憶體,設定共享記憶體大小,這時會得到一個fd。2. 獲取共享記憶體地址。3. 先讀取地址資料,然後往地址寫入資料。4. 把fd通過binder傳送給需要使用的程式。
程式b:1. 通過binder讀取到fd。2. 用fd獲取共享記憶體地址。3. 讀取共享記憶體資料,然後往地址寫入資料。
注意:linux一切皆檔案,所以檔案描述符fd很重要。
3.
3.1
捋清思路後,就可以開始寫程式碼了(android.mk的編寫可以參考之前的文章),程式a,命名為mysharememory_a程式碼如下:
#include <fcntl.h> #include <stdio.h> #include <string.h> #include <linux/ashmem.h> #include <sys/mman.h> #include <sys/ioctl.h> #include <stddef.h> #include <linux/ipc.h> #include <linux/shm.h> #include <binder/IServiceManager.h> #include <binder/IPCThreadState.h> #include <binder/Parcel.h> #include <binder/IInterface.h> #define DEVASHMEM "/dev/ashmem" #define SHNAME "hellomemory" #define MAXBUFSIZE 1024 #define TRANSFDCODE 1000 #define WRITEDATACODE 1001 using namespace android; int main(int argc, char *argv[]) { int fd = open(DEVASHMEM, O_RDWR); if(fd < 0) { return -1; } int ret = ioctl(fd, ASHMEM_SET_NAME, SHNAME); if(ret < 0){ close(fd); return -1; } char *get_sh_addr_write = NULL; ret = ioctl(fd, ASHMEM_SET_SIZE, MAXBUFSIZE); if(ret < 0){ close(fd); return -1; } get_sh_addr_write = (char*)mmap(NULL, MAXBUFSIZE , PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if(NULL == get_sh_addr_write) { return -1; } sp<IServiceManager> sm = defaultServiceManager(); sp<IBinder> binder = sm->checkService(String16("mybindertag")); Parcel data, reply; data.writeDupFileDescriptor(fd); binder->transact(TRANSFDCODE, data, &reply); char input_data[MAXBUFSIZE] = {0}; while(1) { printf("read share memory buf is %s\n", get_sh_addr_write); printf("please input data to buf :"); scanf("%s", input_data); getchar(); strcpy(get_sh_addr_write,input_data); binder->transact(WRITEDATACODE, data, &reply); } return ret; }
3.2
mysharememory_b程式碼如下:
#include <fcntl.h> #include <stdio.h> #include <string.h> #include <linux/ashmem.h> #include <sys/mman.h> #include <sys/ioctl.h> #include <stddef.h> #include <linux/ipc.h> #include <linux/shm.h> #include <binder/IServiceManager.h> #include <binder/IPCThreadState.h> #include <binder/Parcel.h> #include <binder/IInterface.h> #define DEVASHMEM "/dev/ashmem" #define SHNAME "hellomemory" #define MAXBUFSIZE 1024 #define TRANSFDCODE 1000 #define WRITEDATACODE 1001 using namespace android; int g_sh_fd = 0; class MyBinderService : public BBinder{ status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) { int ret; char *get_sh_addr_read = NULL; int get_sh_size; printf("songsong!! **** onTransact ***** code = %d \n",code); switch(code) { case TRANSFDCODE: g_sh_fd = data.readFileDescriptor(); break; case WRITEDATACODE: get_sh_size = ioctl(g_sh_fd, ASHMEM_GET_SIZE,NULL); if(get_sh_size > 0) { get_sh_addr_read = (char*)mmap(NULL, get_sh_size, PROT_READ | PROT_WRITE, MAP_SHARED, g_sh_fd, 0); } else { printf("mmap failed %d\n", get_sh_size); return -1; } printf("what is in the share memory: %s\n", get_sh_addr_read); break; default: break; } return NO_ERROR; } }; int main(int argc, char *argv[]) { defaultServiceManager()->addService(String16("mybindertag"), new MyBinderService()); sp<ProcessState> proc(ProcessState::self()); ProcessState::self()->startThreadPool(); IPCThreadState::self()->joinThreadPool(); return 0; }
3.3
回收關閉部分程式碼可選擇新增在mysharememory_b中,如下:
int ret; ret = munmap((void*)get_sh_addr_read, get_sh_size); if(ret == -1) { return -1; } ret = close(g_sh_fd); if(ret == -1) { return -1; }
3.4
演示截圖:
4. 為了騙取評論,我不再解釋程式碼,心累。不過您可以把程式碼直接拷貝去編譯執行,再通過除錯去理解程式碼的精髓,也是沒問題的。