AshmemAndroid 內(nèi)存分配與共享的增強

      網(wǎng)友投稿 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 方法

      Ashmem 對 Android 內(nèi)存分配與共享的增強

      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)容。

      上一篇:MongoDB 插入文檔
      下一篇:Sphinx 自動化文檔
      相關(guān)文章
      亚洲熟妇av一区二区三区漫画| 亚洲av无码专区在线| 亚洲av无码专区在线电影天堂| 亚洲男人的天堂在线| 亚洲资源在线视频| 亚洲精品美女视频| 亚洲a一级免费视频| 亚洲av无码专区在线播放 | 国产亚洲成AV人片在线观黄桃| 亚洲第一视频在线观看免费| 香蕉视频亚洲一级| 亚洲JIZZJIZZ中国少妇中文| 亚洲av无码无线在线观看| 亚洲aⅴ无码专区在线观看 | 亚洲精品线在线观看| 日韩亚洲Av人人夜夜澡人人爽| 亚洲黄色在线电影| 亚洲人成免费网站| 亚洲一级毛片免费看| 亚洲欧洲免费无码| 亚洲精品又粗又大又爽A片| 亚洲av最新在线观看网址| 亚洲av无码av在线播放| 亚洲A丁香五香天堂网| 中国亚洲女人69内射少妇| 欧洲亚洲国产清在高| 亚洲第一精品福利| 亚洲国产理论片在线播放| 亚洲综合小说另类图片动图| 亚洲色大成网站www| 男人的天堂亚洲一区二区三区 | 色天使亚洲综合在线观看| 亚洲中文字幕无码亚洲成A人片| 亚洲熟妇无码av另类vr影视| 亚洲欧美黑人猛交群| 亚洲国产电影av在线网址| 亚洲中文字幕久久精品无码喷水| 国产亚洲人成网站在线观看不卡| 亚洲嫩草影院久久精品| 亚洲中文字幕人成乱码| 亚洲第一成年免费网站|