Android中的Serializable、Parcelable">Android中的Serializable、Parcelable
1213
2022-05-29
Ashmem 是什么?
Ashmem(Anonymous Shared Memory 匿名共享內(nèi)存),是在 Android 的內(nèi)存管理中提供的一種機制。它基于mmap系統(tǒng)調(diào)用,不同的進程可以將同一段物理內(nèi)存空間映射到各自的虛擬空間,從而實現(xiàn)共享。
mmap機制
mmap系統(tǒng)調(diào)用是將一個打開的文件映射到進程的用戶空間,mmap系統(tǒng)調(diào)用使得進程之間通過映射同一個普通文件實現(xiàn)共享內(nèi)存。普通文件被映射到進程地址空間后,進程可以像訪問普通內(nèi)存一樣對文件進行訪問,不必再調(diào)用read(),write()等操作。
mmap 函數(shù)原型:
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
* addr: *指定為文件描述符fd應(yīng)被映射到的進程空間的起始地址。它通常被指定為一個空指針,這樣告訴內(nèi)核自己去選擇起始地址。一般默認為NULL
* length: *是映射到調(diào)用進程地址空間中的字節(jié)數(shù),從被映射文件開頭offset個字節(jié)處開始算
* prot: *負責(zé)保護內(nèi)存映射區(qū)的保護。常用值是代表讀寫訪問的PROT_READ | PROT_WRITE.當(dāng)然還包括數(shù)據(jù)的執(zhí)行(PROT_EXEC)、數(shù)據(jù)不可訪問(PROT_NONE)
** flag: **flags常用值有MAP_SHARED或MAP_PRIVATE這兩個標(biāo)志必須選一個,并可以選上MAP_FIXED。如果指定了,那么調(diào)用進程對被映射數(shù)據(jù)所做的修改只對該進程可見,而不該變其底層支撐對象。如果指定了,那么調(diào)用進程對被映射數(shù)據(jù)所作的修改對于共享該對象的所有進程都可見,而且確實改變了其底層支撐對象
* fd: *參數(shù)fd為映射文件的描述符,offset為文件的起點,默認為0
* offset: *偏移量
ashmem 在 mmap 上的改進
ashmem通過內(nèi)核驅(qū)動提供了輔助內(nèi)核的內(nèi)存回收算法機制(pin/unpin)
什么是pin和unpin呢?
具體來講,就是當(dāng)你使用Ashmem分配了一塊內(nèi)存,但是其中某些部分卻不會被使用時,那么就可以將這塊內(nèi)存unpin掉。unpin后,內(nèi)核可以將它對應(yīng)的物理頁面回收,以作他用。你也不用擔(dān)心進程無法對unpin掉的內(nèi)存進行再次訪問,因為回收后的內(nèi)存還可以再次被獲得(通過缺頁handler),因為unpin操作并不會改變已經(jīng) mmap的地址空間。
Ashmem 的定義
我們先來看一下部分 ashmem 實現(xiàn)的頭文件(ashmem.h)
#define ASHMEM_NAME_LEN 256 //定義設(shè)備名稱 #define ASHMEM_NAME_DEF "dev/ashmem" /* 從 ASHMEM_PIN 返回的值: 判斷是否要清除 */ #define ASHMEM_NOT_PURGED 0 #define ASHMEM_WAS_PURGED 1 /*從 ASHMEM_GET_PIN_STATUS 返回的值: 是 pinned 還是 unpined */ #define ASHMEM_IS_UNPINNED 0 #define ASHMEM_IS_PINNED 1 struct ashmem_pin { __u32 offset; /* 偏移量 */ __u32 len; /* 從偏移開始的長度 */ };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Ashmem 是怎么實現(xiàn)的?
下面我們開始按照 Ashmem 的實現(xiàn)代碼來看看它是怎么樣工作的(ashmem.c)
我們先來看一下兩個結(jié)構(gòu)體ashmem_area和ashmem_range:
/* * ashmem_area - anonymous shared memory area * Lifecycle: From our parent file's open() until its release() * Locking: Protected by `ashmem_mutex' * Big Note: Mappings do NOT pin this structure; it dies on close() */ struct ashmem_area { char name[ASHMEM_FULL_NAME_LEN]; /* 用于/proc/pid/maps中的一個標(biāo)識名稱(可選) */ struct list_head unpinned_list; /* 所有 ashmem 共享內(nèi)存區(qū)域列表 */ struct file *file; /* ashmem 支持的文件 */ size_t size; /* 區(qū)域字節(jié)大小 */ unsigned long prot_mask; /* 內(nèi)存映射區(qū)的保護 */ };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
我們可以看到 ashmem_area 定義了一個內(nèi)存共享區(qū)域,它的生命周期是從文件打開open()到它被釋放release(),并且支持原子性
/* * ashmem_range - represents an interval of unpinned (evictable) pages * Lifecycle: From unpin to pin * Locking: Protected by `ashmem_mutex' */ struct ashmem_range { struct list_head lru; /* LRU 列表 */ struct list_head unpinned; /* unpinned 列表 */ struct ashmem_area *asma; /* 關(guān)聯(lián)的 ashmem 區(qū)域 */ size_t pgstart; /* 開始頁面 */ size_t pgend; /* 結(jié)束頁面 */ unsigned int purged; /* 是否要被回收 */ };
1
2
3
4
5
6
7
8
9
10
11
12
13
我們看到ashmem_range的生命周期是從 unpin 到 pin
初始化 - ashmem_init(void)
static int __init ashmem_init(void) { int ret; ashmem_area_cachep = kmem_cache_create("ashmem_area_cache", sizeof(struct ashmem_area), 0, 0, NULL); if (unlikely(!ashmem_area_cachep)) { pr_err("failed to create slab cache\n"); return -ENOMEM; } ashmem_range_cachep = kmem_cache_create("ashmem_range_cache", sizeof(struct ashmem_range), 0, 0, NULL); if (unlikely(!ashmem_range_cachep)) { pr_err("failed to create slab cache\n"); return -ENOMEM; } ret = misc_register(&ashmem_misc); if (unlikely(ret)) { pr_err("failed to register misc device!\n"); return ret; } register_shrinker(&ashmem_shrinker); pr_info("initialized\n"); return 0; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
我們從代碼中可以看到初始化函數(shù)ashmem_init(void)主要做了以下幾件事:
通過kmem_cache_create1創(chuàng)建 ahemem_area 高速緩存
通過kmem_cache_create創(chuàng)建 ahemem_range 高速緩存
通過misc_register將 Ashmem 注冊為 misc 設(shè)備2
通過register_shrinker注冊回收函數(shù)
退出 - ashmem_exit(void)
static void __exit ashmem_exit(void) { int ret; unregister_shrinker(&ashmem_shrinker); ret = misc_deregister(&ashmem_misc); if (unlikely(ret)) pr_err("failed to unregister misc device!\n"); kmem_cache_destroy(ashmem_range_cachep); kmem_cache_destroy(ashmem_area_cachep); pr_info("unloaded\n"); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
我們在代碼中看到了所有在退出時所做的操作:
卸載回收函數(shù)unregister_shrinker
卸載設(shè)備misc_deregister
回收兩段高速緩存(ashmem_area & ashmem_range)kmem_cache_destroy
對內(nèi)存進行分配、釋放和回收
我們先看看Ashmem分配內(nèi)存的流程:
打開“/dev/ashmem”文件
通過ioctl來設(shè)置名稱和大小等
調(diào)用mmap將Ashmem分配的空間映射到進程空間
打開多少次/dev/ashmem設(shè)備并mmap,就會獲得多少個不同的空間
我們在初始化Ashmem時注冊了Ashmem設(shè)備,其中包含的相關(guān)方法及其作用如下面的代碼所示:
static const struct file_operations ashmem_fops = { .owner = THIS_MODULE, .open = ashmem_open, .release = ashmem_release, .read = ashmem_read, .llseek = ashmem_llseek, .mmap = ashmem_mmap, .unlocked_ioctl = ashmem_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = compat_ashmem_ioctl, #endif }; static struct miscdevice ashmem_misc = { .minor = MISC_DYNAMIC_MINOR, .name = "ashmem", .fops = &ashmem_fops, };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
其中,ashmem_open方法主要是對unpinned列表進行初始化,并將Ashmem分配的地址空間賦給file結(jié)構(gòu)的private_data,這就排除了進程間共享的可能性。ashmem_release方法用于將指定的節(jié)點的空間從鏈表中刪除并釋放掉
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 (unlikely(ret)) return ret; asma = kmem_cache_zalloc(ashmem_area_cachep, GFP_KERNEL); if (unlikely(!asma)) return -ENOMEM; INIT_LIST_HEAD(&asma->unpinned_list); memcpy(asma->name, ASHMEM_NAME_PREFIX, ASHMEM_NAME_PREFIX_LEN); asma->prot_mask = PROT_MASK; file->private_data = asma; return 0; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ashmem_release 方法
static int ashmem_release(struct inode *ignored, struct file *file) { struct ashmem_area *asma = file->private_data; struct ashmem_range *range, *next; mutex_lock(&ashmem_mutex); list_for_each_entry_safe(range, next, &asma->unpinned_list, unpinned) range_del(range); mutex_unlock(&ashmem_mutex); if (asma->file) fput(asma->file); kmem_cache_free(ashmem_area_cachep, asma); return 0; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
需要指出的是,當(dāng)使用list_for_each_entry_safe(pos, n, head,member)函數(shù)時,需要調(diào)用者另外提供一個與pos同類型的指針n,在for循環(huán)中暫存pos節(jié)點的下一個節(jié)點的地址,避免因pos節(jié)點被釋放而造成斷鏈
接下來就是將分配的空間映射到進程空間。在ashmem_mmap函數(shù)中需要指出的是,它借助了Linux內(nèi)核的shmem_file_setup(支撐文件)工具,使得我們不需要自己去實現(xiàn)這一復(fù)雜的過程。所以ashmem_mmap的整個實現(xiàn)過程很簡單,大家可以參考它的源代碼:
static int ashmem_mmap(struct file *file, struct vm_area_struct *vma) { struct ashmem_area *asma = file->private_data; int ret = 0; mutex_lock(&ashmem_mutex); /* user needs to SET_SIZE before mapping */ if (unlikely(!asma->size)) { ret = -EINVAL; goto out; } /* requested protection bits must match our allowed protection mask */ if (unlikely((vma->vm_flags & ~calc_vm_prot_bits(asma->prot_mask)) & calc_vm_prot_bits(PROT_MASK))) { ret = -EPERM; goto out; } vma->vm_flags &= ~calc_vm_may_flags(~asma->prot_mask); if (!asma->file) { char *name = ASHMEM_NAME_DEF; struct file *vmfile; 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); if (unlikely(IS_ERR(vmfile))) { ret = PTR_ERR(vmfile); goto out; } asma->file = vmfile; } get_file(asma->file); if (vma->vm_flags & VM_SHARED) shmem_set_file(vma, asma->file); else { if (vma->vm_file) fput(vma->vm_file); vma->vm_file = asma->file; } out: mutex_unlock(&ashmem_mutex); return ret; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
最后,我們還將分析通過ioctl來pin和unpin某一段映射的空間的實現(xiàn)方式。ashmem_ioctl函數(shù)的功能很多,它可以通過其參數(shù)cmd來處理不同的操作,包括設(shè)置(獲取)名稱和尺寸、pin/unpin以及獲取pin的一些狀態(tài)。最終對pin/unpin的處理會通過下面這個函數(shù)來完成:
static int ashmem_pin(struct ashmem_area *asma, size_t pgstart, size_t pgend) { struct ashmem_range *range, *next; int ret = ASHMEM_NOT_PURGED; list_for_each_entry_safe(range, next, &asma->unpinned_list, unpinned) { /* moved past last applicable page; we can short circuit */ if (range_before_page(range, pgstart)) break; /* * The user can ask us to pin pages that span multiple ranges, * or to pin pages that aren't even unpinned, so this is messy. * * Four cases: * 1. The requested range subsumes an existing range, so we * just remove the entire matching range. * 2. The requested range overlaps the start of an existing * range, so we just update that range. * 3. The requested range overlaps the end of an existing * range, so we just update that range. * 4. The requested range punches a hole in an existing range, * so we have to update one side of the range and then * create a new range for the other side. */ if (page_range_in_range(range, pgstart, pgend)) { ret |= range->purged; /* Case #1: Easy. Just nuke the whole thing. */ if (page_range_subsumes_range(range, pgstart, pgend)) { range_del(range); continue; } /* Case #2: We overlap from the start, so adjust it */ if (range->pgstart >= pgstart) { range_shrink(range, pgend + 1, range->pgend); continue; } /* Case #3: We overlap from the rear, so adjust it */ if (range->pgend <= pgend) { range_shrink(range, range->pgstart, pgstart-1); continue; } range_alloc(asma, range, range->purged, pgend + 1, range->pgend); range_shrink(range, range->pgstart, pgstart - 1); break; } } return ret; } static int ashmem_unpin(struct ashmem_area *asma, size_t pgstart, size_t pgend) { struct ashmem_range *range, *next; unsigned int purged = ASHMEM_NOT_PURGED; restart: list_for_each_entry_safe(range, next, &asma->unpinned_list, unpinned) { /* short circuit: this is our insertion point */ if (range_before_page(range, pgstart)) break; if (page_range_subsumed_by_range(range, pgstart, pgend)) return 0; if (page_range_in_range(range, pgstart, pgend)) { pgstart = min_t(size_t, range->pgstart, pgstart), pgend = max_t(size_t, range->pgend, pgend); purged |= range->purged; range_del(range); goto restart; } } return range_alloc(asma, range, purged, pgstart, pgend); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
最后需要說明:回收函數(shù)cache_shrinker同樣也參考了Linux內(nèi)核的slab分配算法用于頁面回收的回調(diào)函數(shù)。具體實現(xiàn)如下:
static int ashmem_shrink(struct shrinker *s, struct shrink_control *sc) { struct ashmem_range *range, *next; /* We might recurse into filesystem code, so bail out if necessary */ if (sc->nr_to_scan && !(sc->gfp_mask & __GFP_FS)) return -1; if (!sc->nr_to_scan) return lru_count; if (!mutex_trylock(&ashmem_mutex)) return -1; list_for_each_entry_safe(range, next, &ashmem_lru_list, lru) { loff_t start = range->pgstart * PAGE_SIZE; loff_t end = (range->pgend + 1) * PAGE_SIZE; range->asma->file->f_op->fallocate(range->asma->file, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, start, end - start); range->purged = ASHMEM_WAS_PURGED; lru_del(range); sc->nr_to_scan -= range_size(range); if (sc->nr_to_scan <= 0) break; } mutex_unlock(&ashmem_mutex); return lru_count; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
cache_shrinker同樣先取得了ashmem_mutex,通過list_for_each_entry_safe來確保其被安全釋放。該方法會被mm/vmscan.c :: shrink_slab調(diào)用,其中參數(shù)nr_to_scan表示有多少個頁面對象。如果該參數(shù)為0,則表示查詢所有的頁面對象總數(shù)。而“gfp_mask”是一個配置,返回值為被回收之后剩下的頁面數(shù)量;如果返回-1,則表示由于配置文件(gfp_mask)產(chǎn)生的問題,使得mutex_lock不能進行安全的死鎖
本文所分析的代碼為 Android 3.10 版本代碼(ashmem.h3 & ashmem.c4)
====
kmem_cache_create (const char *name, size_t size, size_t align, unsigned long flags,void (*ctor)(void*, struct kmem_cache *, unsigned long)) 用于創(chuàng)建 SLAB 高速緩存 ?
Minimal instruction set computer ?
https://github.com/android/kernel_common/blob/android-3.10/drivers/staging/android/ashmem.c ?
https://github.com/android/kernel_common/blob/android-3.10/drivers/staging/android/uapi/ashmem.h ?
Android
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。