Android 匿名內(nèi)存深入分析
Android 匿名內(nèi)存解析
有了binder機(jī)制為什么還需要匿名內(nèi)存來(lái)實(shí)現(xiàn)IPC呢?我覺(jué)得很大的原因就是binder傳輸是有大小限制的,不說(shuō)應(yīng)用層的限制。在驅(qū)動(dòng)中binder的傳輸大小被限制在了4M,分享一張圖片可能就超過(guò)了這個(gè)限制。匿名內(nèi)存的主要解決思路就是通過(guò)binder傳輸文件描述符,使得兩個(gè)進(jìn)程都能訪問(wèn)同一個(gè)地址來(lái)實(shí)現(xiàn)共享。
MemoryFile使用
在平常開(kāi)發(fā)中android提供了MemoryFile來(lái)實(shí)現(xiàn)匿名內(nèi)存??聪伦詈?jiǎn)單的實(shí)現(xiàn)。
Service端
?
const val GET_ASH_MEMORY = 1000
class MyService : Service() {
val ashData = "AshDemo".toByteArray()
override fun onBind(intent: Intent): IBinder {
return object : Binder() {
override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
when(code){
GET_ASH_MEMORY->{//收到客戶端請(qǐng)求的時(shí)候會(huì)煩
val descriptor = createMemoryFile()
reply?.writeParcelable(descriptor, 0)
reply?.writeInt(ashData.size)
return true
}
else->{
return super.onTransact(code, data, reply, flags)
}
}
}
}
}
private fun createMemoryFile(): ParcelFileDescriptor? {
val file = MemoryFile("AshFile", 1024)//創(chuàng)建MemoryFile
val descriptorMethod = file.javaClass.getDeclaredMethod("getFileDescriptor")
val fd=descriptorMethod.invoke(file)//反射拿到fd
file.writeBytes(ashData, 0, 0,ashData.size)//寫(xiě)入字符串
return ParcelFileDescriptor.dup(fd as FileDescriptor?)//返回一個(gè)封裝的fd
}
}
Server的功能很簡(jiǎn)單收到GET_ASH_MEMORY請(qǐng)求的時(shí)候創(chuàng)建一個(gè)MemoryFile,往里寫(xiě)入一個(gè)字符串的byte數(shù)組,然后將fd和字符長(zhǎng)度寫(xiě)入reply中返回給客戶端。
Client端
?
class MainActivity : AppCompatActivity() {
val connect = object :ServiceConnection{
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val reply = Parcel.obtain()
val sendData = Parcel.obtain()
service?.transact(GET_ASH_MEMORY, sendData, reply, 0)//傳輸信號(hào)GET_ASH_MEMORY
val pfd = reply.readParcelable<ParcelFileDescriptor>(javaClass.classLoader)
val descriptor = pfd?.fileDescriptor//拿到fd
val size = reply.readInt()//拿到長(zhǎng)度
val input = FileInputStream(descriptor)
val bytes = input.readBytes()
val message = String(bytes, 0, size, Charsets.UTF_8)//生成string
Toast.makeText(this@MainActivity,message,Toast.LENGTH_SHORT).show()
}
?
override fun onServiceDisconnected(name: ComponentName?) {
}
?
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<TextView>(R.id.intent).setOnClickListener {
//啟動(dòng)服務(wù)
bindService(Intent(this,MyService::class.java),connect, Context.BIND_AUTO_CREATE)
}
}
}
客戶端也很簡(jiǎn)單,啟動(dòng)服務(wù),發(fā)送一個(gè)獲取MemoryFile的請(qǐng)求,然后通過(guò)reply拿到fd和長(zhǎng)度,用FileInputStream讀取fd中的內(nèi)容,最后通過(guò)toast可以驗(yàn)證這個(gè)message已經(jīng)拿到了。
AshMemory 創(chuàng)建原理
public MemoryFile(String name, int length) throws IOException {
try {
mSharedMemory = SharedMemory.create(name, length);
mMapping = mSharedMemory.mapReadWrite();
} catch (ErrnoException ex) {
ex.rethrowAsIOException();
}
}
MemoryFile就是對(duì)SharedMemory的一層封裝,具體的工能都是SharedMemory實(shí)現(xiàn)的??碨haredMemory的實(shí)現(xiàn)。
public static @NonNull SharedMemory create(@Nullable String name, int size)
throws ErrnoException {
if (size <= 0) {
throw new IllegalArgumentException("Size must be greater than zero");
}
return new SharedMemory(nCreate(name, size));
}
private static native FileDescriptor nCreate(String name, int size) throws ErrnoException;
通過(guò)一個(gè)JNI獲得fd,從這里可以推斷出java層也只是一個(gè)封裝,拿到的已經(jīng)是創(chuàng)建好的fd。
//frameworks/base/core/jni/android_os_SharedMemory.cpp
jobject SharedMemory_nCreate(JNIEnv* env, jobject, jstring jname, jint size) {
const char* name = jname ? env->GetStringUTFChars(jname, nullptr) : nullptr;
int fd = ashmem_create_region(name, size);//創(chuàng)建匿名內(nèi)存塊
int err = fd < 0 ? errno : 0;
if (name) {
env->ReleaseStringUTFChars(jname, name);
}
if (fd < 0) {
jniThrowErrnoException(env, "SharedMemory_create", err);
return nullptr;
}
jobject jifd = jniCreateFileDescriptor(env, fd);//創(chuàng)建java fd返回
if (jifd == nullptr) {
close(fd);
}
return jifd;
}
通過(guò)cutils中的ashmem_create_region函數(shù)實(shí)現(xiàn)的創(chuàng)建
//system/core/libcutils/ashmem-dev.cpp
int ashmem_create_region(const char *name, size_t size)
{
int ret, save_errno;
?
if (has_memfd_support()) {//老版本兼容用
return memfd_create_region(name ? name : "none", size);
}
?
int fd = __ashmem_open();//打開(kāi)Ashmem驅(qū)動(dòng)
if (fd < 0) {
return fd;
}
if (name) {
char buf[ASHMEM_NAME_LEN] = {0};
strlcpy(buf, name, sizeof(buf));
ret = TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_SET_NAME, buf));//通過(guò)ioctl設(shè)置名字
if (ret < 0) {
goto error;
}
}
ret = TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_SET_SIZE, size));//通過(guò)ioctl設(shè)置大小
if (ret < 0) {
goto error;
}
return fd;
error:
save_errno = errno;
close(fd);
errno = save_errno;
return ret;
}
?
標(biāo)準(zhǔn)的驅(qū)動(dòng)交互操作
1.open打開(kāi)驅(qū)動(dòng)
2.通過(guò)ioctl與驅(qū)動(dòng)進(jìn)行交互
下面看下open的流程
static int __ashmem_open()
{
int fd;
?
pthread_mutex_lock(&__ashmem_lock);
fd = __ashmem_open_locked();
pthread_mutex_unlock(&__ashmem_lock);
?
return fd;
}
?
/* logistics of getting file descriptor for ashmem */
static int __ashmem_open_locked()
{
static const std::string ashmem_device_path = get_ashmem_device_path();//拿到Ashmem驅(qū)動(dòng)路徑
if (ashmem_device_path.empty()) {
return -1;
}
int fd = TEMP_FAILURE_RETRY(open(ashmem_device_path.c_str(), O_RDWR | O_CLOEXEC));
return fd;
}
回到MemoryFile的構(gòu)造函數(shù)中,拿到了驅(qū)動(dòng)的fd之后調(diào)用了mapReadWrite
public @NonNull ByteBuffer mapReadWrite() throws ErrnoException {
return map(OsConstants.PROT_READ | OsConstants.PROT_WRITE, 0, mSize);
}
public @NonNull ByteBuffer map(int prot, int offset, int length) throws ErrnoException {
checkOpen();
validateProt(prot);
if (offset < 0) {
throw new IllegalArgumentException("Offset must be >= 0");
}
if (length <= 0) {
throw new IllegalArgumentException("Length must be > 0");
}
if (offset + length > mSize) {
throw new IllegalArgumentException("offset + length must not exceed getSize()");
}
long address = Os.mmap(0, length, prot, OsConstants.MAP_SHARED, mFileDescriptor, offset);//調(diào)用了系統(tǒng)的mmap
boolean readOnly = (prot & OsConstants.PROT_WRITE) == 0;
Runnable unmapper = new Unmapper(address, length, mMemoryRegistration.acquire());
return new DirectByteBuffer(length, address, mFileDescriptor, unmapper, readOnly);
}
?
到這里就有一個(gè)疑問(wèn),Linux就有共享內(nèi)存,android為什么要自己搞一套,只能看下Ashmemory驅(qū)動(dòng)的實(shí)現(xiàn)了。
驅(qū)動(dòng)第一步看init和file_operations
static int __init ashmem_init(void)
{
int ret = -ENOMEM;
?
ashmem_area_cachep = kmem_cache_create("ashmem_area_cache",
sizeof(struct ashmem_area),
0, 0, NULL);//創(chuàng)建
if (!ashmem_area_cachep) {
pr_err("failed to create slab cache\n");
goto out;
}
?
ashmem_range_cachep = kmem_cache_create("ashmem_range_cache",
sizeof(struct ashmem_range),
0, SLAB_RECLAIM_ACCOUNT, NULL);//創(chuàng)建
if (!ashmem_range_cachep) {
pr_err("failed to create slab cache\n");
goto out_free1;
}
?
ret = misc_register(&ashmem_misc);//注冊(cè)為了一個(gè)misc設(shè)備
........
return ret;
}
創(chuàng)建了兩個(gè)內(nèi)存分配器ashmem_area_cachep和ashmem_range_cachep用于分配ashmem_area和ashmem_range
//common/drivers/staging/android/ashmem.c
static const struct file_operations ashmem_fops = {
.owner = THIS_MODULE,
.open = ashmem_open,
.release = ashmem_release,
.read_iter = ashmem_read_iter,
.llseek = ashmem_llseek,
.mmap = ashmem_mmap,
.unlocked_ioctl = ashmem_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = compat_ashmem_ioctl,
#endif
#ifdef CONFIG_PROC_FS
.show_fdinfo = ashmem_show_fdinfo,
#endif
};
?
open調(diào)用的就是ashmem_open
static int ashmem_open(struct inode *inode, struct file *file)
{
struct ashmem_area *asma;
int ret;
?
ret = generic_file_open(inode, file);
if (ret)
return ret;
?
asma = kmem_cache_zalloc(ashmem_area_cachep, GFP_KERNEL);//分配一個(gè)ashmem_area
if (!asma)
return -ENOMEM;
?
INIT_LIST_HEAD(&asma->unpinned_list);//初始化unpinned_list
memcpy(asma->name, ASHMEM_NAME_PREFIX, ASHMEM_NAME_PREFIX_LEN);//初始化一個(gè)名字
asma->prot_mask = PROT_MASK;
file->private_data = asma;
return 0;
}
ioctl設(shè)置名字和長(zhǎng)度調(diào)用的就是ashmem_ioctl
static long ashmem_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct ashmem_area *asma = file->private_data;
long ret = -ENOTTY;
?
switch (cmd) {
case ASHMEM_SET_NAME:
ret = set_name(asma, (void __user *)arg);
break;
case ASHMEM_SET_SIZE:
ret = -EINVAL;
mutex_lock(&ashmem_mutex);
if (!asma->file) {
ret = 0;
asma->size = (size_t)arg;
}
mutex_unlock(&ashmem_mutex);
break;
}
........
}
實(shí)現(xiàn)也都很簡(jiǎn)單就是改變了一下asma里的值。接下來(lái)就是重點(diǎn)mmap了,具體是怎么分配內(nèi)存的。
?static int ashmem_mmap(struct file *file, struct vm_area_struct *vma)
{
static struct file_operations vmfile_fops;
struct ashmem_area *asma = file->private_data;
int ret = 0;
?
mutex_lock(&ashmem_mutex);
?
/* user needs to SET_SIZE before mapping */
if (!asma->size) {//判斷設(shè)置了size
ret = -EINVAL;
goto out;
}
?
/* requested mapping size larger than object size */
if (vma->vm_end - vma->vm_start > PAGE_ALIGN(asma->size)) {//判斷大小是否超過(guò)了虛擬內(nèi)存
ret = -EINVAL;
goto out;
}
?
/* requested protection bits must match our allowed protection mask */
if ((vma->vm_flags & ~calc_vm_prot_bits(asma->prot_mask, 0)) &
calc_vm_prot_bits(PROT_MASK, 0)) {//權(quán)限判斷
ret = -EPERM;
goto out;
}
vma->vm_flags &= ~calc_vm_may_flags(~asma->prot_mask);
?
if (!asma->file) {//是否創(chuàng)建過(guò)臨時(shí)文件,沒(méi)創(chuàng)建過(guò)進(jìn)入
char *name = ASHMEM_NAME_DEF;
struct file *vmfile;
struct inode *inode;
?
if (asma->name[ASHMEM_NAME_PREFIX_LEN] != '\0')
name = asma->name;
?
/* ... and allocate the backing shmem file */
vmfile = shmem_file_setup(name, asma->size, vma->vm_flags);//調(diào)用linux函數(shù)在tmpfs中創(chuàng)建臨時(shí)文件
if (IS_ERR(vmfile)) {
ret = PTR_ERR(vmfile);
goto out;
}
vmfile->f_mode |= FMODE_LSEEK;
inode = file_inode(vmfile);
lockdep_set_class(&inode->i_rwsem, &backing_shmem_inode_class);
asma->file = vmfile;
/*
* override mmap operation of the vmfile so that it can't be
* remapped which would lead to creation of a new vma with no
* asma permission checks. Have to override get_unmapped_area
* as well to prevent VM_BUG_ON check for f_ops modification.
*/
if (!vmfile_fops.mmap) {//設(shè)置了臨時(shí)文件的文件操作,防止有其他程序mmap這個(gè)臨時(shí)文件
vmfile_fops = *vmfile->f_op;
vmfile_fops.mmap = ashmem_vmfile_mmap;
vmfile_fops.get_unmapped_area =
ashmem_vmfile_get_unmapped_area;
}
vmfile->f_op = &vmfile_fops;
}
get_file(asma->file);
?
/*
* XXX - Reworked to use shmem_zero_setup() instead of
* shmem_set_file while we're in staging. -jstultz
*/
if (vma->vm_flags & VM_SHARED) {//這塊內(nèi)存是不是需要跨進(jìn)程
ret = shmem_zero_setup(vma);//設(shè)置文件
if (ret) {
fput(asma->file);
goto out;
}
} else {
/**
實(shí)現(xiàn)就是把vm_ops設(shè)置為NULL
static inline void vma_set_anonymous(struct vm_area_struct *vma)
{
vma->vm_ops = NULL;
}
*/
vma_set_anonymous(vma);
}
?
vma_set_file(vma, asma->file);
/* XXX: merge this with the get_file() above if possible */
fput(asma->file);
?
out:
mutex_unlock(&ashmem_mutex);
return ret;
}
函數(shù)很長(zhǎng),但是思路還是很清晰的。創(chuàng)建臨時(shí)文件,設(shè)置文件操作。其中調(diào)用的都是linux的系統(tǒng)函數(shù)了,看真正設(shè)置的shmem_zero_setup函數(shù)
int shmem_zero_setup(struct vm_area_struct *vma)
{
struct file *file;
loff_t size = vma->vm_end - vma->vm_start;
?
/*
* Cloning a new file under mmap_lock leads to a lock ordering conflict
* between XFS directory reading and selinux: since this file is only
* accessible to the user through its mapping, use S_PRIVATE flag to
* bypass file security, in the same way as shmem_kernel_file_setup().
*/
file = shmem_kernel_file_setup("dev/zero", size, vma->vm_flags);
if (IS_ERR(file))
return PTR_ERR(file);
?
if (vma->vm_file)
fput(vma->vm_file);
vma->vm_file = file;
vma->vm_ops = &shmem_vm_ops;//很重要的操作將這塊虛擬內(nèi)存的vm_ops設(shè)置為shmem_vm_ops
?
if (IS_ENABLED(CONFIG_TRANSPARENT_HUGEPAGE) &&
((vma->vm_start + ~HPAGE_PMD_MASK) & HPAGE_PMD_MASK) <
(vma->vm_end & HPAGE_PMD_MASK)) {
khugepaged_enter(vma, vma->vm_flags);
}
?
return 0;
}
static const struct vm_operations_struct shmem_vm_ops = {
.fault = shmem_fault,//Linux的共享內(nèi)存實(shí)現(xiàn)的基礎(chǔ)
.map_pages = filemap_map_pages,
#ifdef CONFIG_NUMA
.set_policy = shmem_set_policy,
.get_policy = shmem_get_policy,
#endif
};
到這里共享內(nèi)存的初始化就結(jié)束了。
AshMemory 讀寫(xiě)
?//frameworks/base/core/java/android/os/MemoryFile.java
public void writeBytes(byte[] buffer, int srcOffset, int destOffset, int count)
throws IOException {
beginAccess();
try {
mMapping.position(destOffset);
mMapping.put(buffer, srcOffset, count);
} finally {
endAccess();
}
}
private void beginAccess() throws IOException {
checkActive();
if (mAllowPurging) {
if (native_pin(mSharedMemory.getFileDescriptor(), true)) {
throw new IOException("MemoryFile has been purged");
}
}
}
?
private void endAccess() throws IOException {
if (mAllowPurging) {
native_pin(mSharedMemory.getFileDescriptor(), false);
}
}
其中beginAccess和endAccess是對(duì)應(yīng)的。調(diào)用的都是native_pin是一個(gè)native函數(shù),一個(gè)參數(shù)是true一個(gè)是false。pin的作用就是鎖住這塊內(nèi)存不被系統(tǒng)回收,當(dāng)不使用的時(shí)候就解鎖。
static jboolean android_os_MemoryFile_pin(JNIEnv* env, jobject clazz, jobject fileDescriptor,
jboolean pin) {
int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
int result = (pin ? ashmem_pin_region(fd, 0, 0) : ashmem_unpin_region(fd, 0, 0));
if (result < 0) {
jniThrowException(env, "java/io/IOException", NULL);
}
return result == ASHMEM_WAS_PURGED;
}
調(diào)用的ashmem_pin_region和ashmem_unpin_region來(lái)實(shí)現(xiàn)解鎖和解鎖。實(shí)現(xiàn)還是在ashmem-dev.cpp
//system/core/libcutils/ashmem-dev.cpp
int ashmem_pin_region(int fd, size_t offset, size_t len)
{
.......
ashmem_pin pin = { static_cast<uint32_t>(offset), static_cast<uint32_t>(len) };
return __ashmem_check_failure(fd, TEMP_FAILURE_RETRY(ioctl(fd, ASHMEM_PIN, &pin)));
}
通過(guò)的也是ioclt通知的驅(qū)動(dòng)。加鎖的細(xì)節(jié)就不展開(kāi)了。具體的寫(xiě)入就是利用linux的共享內(nèi)存機(jī)制實(shí)現(xiàn)的共享。
Linux共享機(jī)制簡(jiǎn)介
共享簡(jiǎn)單的實(shí)現(xiàn)方式就是通過(guò)mmap同一個(gè)文件來(lái)實(shí)現(xiàn)。但是真實(shí)文件的讀寫(xiě)速度實(shí)在是太慢了,所以利用tmpfs這個(gè)虛擬文件系統(tǒng),創(chuàng)建了一個(gè)虛擬文件來(lái)讀寫(xiě)。同時(shí)這塊虛擬內(nèi)存在上面也寫(xiě)到重寫(xiě)了vm_ops。當(dāng)有進(jìn)程操作這個(gè)虛擬內(nèi)存的時(shí)候會(huì)觸發(fā)缺頁(yè)錯(cuò)誤,接著會(huì)去查找Page緩存,由于是第一次所以沒(méi)有緩存,讀取物理內(nèi)存,同時(shí)加入Page緩存,當(dāng)?shù)诙€(gè)進(jìn)程進(jìn)來(lái)的時(shí)也觸發(fā)缺頁(yè)錯(cuò)誤時(shí)就能找到Page緩存了,那么他們操作的就是同一塊物理內(nèi)存了。
總結(jié)
看完之后發(fā)現(xiàn)AshMemory是基于Linux的共享內(nèi)存實(shí)現(xiàn)的。做了幾點(diǎn)改造
- 首先把一整塊內(nèi)存變成了一個(gè)個(gè)region,這樣在不用的時(shí)候可以解鎖來(lái)讓系統(tǒng)回收。
- 將Linux共享內(nèi)存的整數(shù)標(biāo)記共享內(nèi)存,而AshMemory是用的fd,讓它可以利用binder機(jī)制的fd傳輸。
- 讀寫(xiě)設(shè)置都做了加鎖的處理,減少了用戶使用的難度。
以上就是Android 匿名內(nèi)存深入分析的詳細(xì)內(nèi)容,更多關(guān)于Android 匿名內(nèi)存的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
實(shí)例解析Android ImageView的scaleType屬性
通過(guò)本文給大家介紹ImageView這個(gè)控件的一些使用方法,以及其最重要的一個(gè)屬性: scaleType,對(duì)imageview的scaletype相關(guān)知識(shí)感興趣的朋友一起學(xué)習(xí)吧2016-01-01
Android Notification.Builder通知案例分享
這篇文章主要為大家分享了Android Notification.Builder通知案例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10
設(shè)置界面開(kāi)發(fā)Preference Library數(shù)據(jù)重建機(jī)制詳解
這篇文章主要為大家介紹了設(shè)置界面開(kāi)發(fā)利器Preference Library數(shù)據(jù)重建機(jī)制詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10
Flutter實(shí)現(xiàn)滑動(dòng)塊驗(yàn)證碼功能
這篇文章主要為大家詳細(xì)介紹了Flutter實(shí)現(xiàn)滑動(dòng)塊驗(yàn)證碼功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
Android 仿蘋(píng)果IOS6開(kāi)關(guān)按鈕
這篇文章主要介紹了Android 仿蘋(píng)果IOS6開(kāi)關(guān)按鈕的實(shí)現(xiàn)代碼,代碼簡(jiǎn)單易懂非常不錯(cuò)具有參考借鑒價(jià)值,需要的朋友可以參考下2016-10-10
Android編程之ListPreference用法實(shí)例分析
這篇文章主要介紹了Android編程之ListPreference用法,結(jié)合實(shí)例形式較為詳細(xì)的分析說(shuō)明了ListPreference的功能、用法及相關(guān)注意事項(xiàng),需要的朋友可以參考下2015-12-12
Android仿美團(tuán)分類下拉菜單實(shí)例代碼
這篇文章主要為大家詳細(xì)介紹了Android仿美團(tuán)分類下拉菜單實(shí)例代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-05-05
android設(shè)備不識(shí)別awk命令 缺少busybox怎么辦
這篇文章主要為大家詳細(xì)介紹了android設(shè)備不識(shí)別awk命令,缺少busybox的解決方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04
自定義Adapter并通過(guò)布局泵LayoutInflater抓取layout模板編輯每一個(gè)item實(shí)現(xiàn)思路
自定義Adapter并通過(guò)布局泵LayoutInflater抓取layout模板編輯每一個(gè)item,下面我們開(kāi)始學(xué)習(xí)這一篇的內(nèi)容,感興趣的朋友可以了解下哈2013-06-06
Android實(shí)現(xiàn)自定義標(biāo)題欄的方法
這篇文章主要介紹了Android實(shí)現(xiàn)自定義標(biāo)題欄的方法,需要的朋友可以參考下2015-12-12

