內存管理 —— ION

ION 是當前 Android 流行的內存分配管理機制,在多媒體部分中使用的最多,例如從 Camera 到 Display,從 Mediaserver 到 Surfaceflinger,都會利用 ION 進行內存分配管理。 ION 的前任是 PMEM,關於 PMEM 我在 M030/M04X 項目中有接觸過,後來由於 PMEM 的一些侷限性,Google 推出了 ION 來取代 PMEM,當前 ION 已經融合到 Linux 主線,被廣泛使用。 對於魅族,從 M65 項目開始,相機的內存分配管理就已經利用 ION 進行了,本文會結合 M65、M76 和 M86 項目開發介紹下我對 ION 內存管理機制的理解和感悟。

ION 基本概念

ION,最顯著的特點是它可以被用戶空間的進程之間或者內核空間的模塊之間進行內存共享,而且這種共享可以是零拷貝的。在實際使用中,ION 和 VIDEOBUF2、DMA-BUF、V4L2 等結合的很緊密。本文主要介紹 ION,其它子系統感興趣的話後續會陸續進行介紹。

ION 是在各種 heaps 上分配內存,通過 ion_buffer 來描述所分配的內存。

下圖展示了 ION 的基本框架。圖中 PID1、PID2、PID3 表示用戶空間進程。ION core 表示 ION 核心層,它提供設備創建、註冊等服務,同時提供統一的接口給用戶使用。ION Driver 利用 ION core 對相應功能進行實現,可以說它是具體平臺相關的,例如 SAMSUNG 平臺、QUALCOMM 平臺和 MTK 平臺都會依據自己的特性開發相應的 ION Driver。
這裏寫圖片描述
上圖雖然描述的是用戶空間進程使用 ION 的情形,但實際上,在內核空間同樣可以直接使用 ION 來分配、管理內存。例如 M76、M86 平臺的相機驅動,都有直接使用 ION 分配和管理內存。

主要數據結構

數據結構是程序設計的基礎,代碼看的多了,其實可以從數據結構看出其能提供的基本功能和大致用法。 爲了抓住綱領,本文將抓住 ION 的主要數據結構進行介紹

ion_device

ion_device 是 ION 很重要很基礎的數據結構,用 struct ion_device 結構體描述,一般在一個系統中只有一個本實例。例如在 M86 中,就是在 exynos_ion_v2.c 的 exynos_ion_probe() 函數中創建了系統唯一的本實例。

struct ion_device {
    struct miscdevice dev;
    struct rb_root buffers;
    struct mutex buffer_lock;
    struct rw_semaphore lock;
    struct plist_head heaps;
    long (*custom_ioctl)(struct ion_client *client, unsigned int cmd,
                 unsigned long arg);
    struct rb_root clients;
    struct dentry *debug_root;
    struct dentry *heaps_debug_root;
    struct dentry *clients_debug_root;
    struct semaphore vm_sem;
    atomic_t page_idx;
    struct vm_struct *reserved_vm_area;
    pte_t **pte;
    ...
};

struct ion_device 其實是 ION 的核心結構,不過由於對於使用 ION 的用戶而言是屏蔽的,即如果是單純使用 ION,不需要直接和本結構打交道。但是想完全的理解 ION,需要對其有所瞭解。 struct ion_device 是由 ion_device_create() [ion.c] 分配、初始化。

  • dev 成員,是 struct miscdevice 類型,所以可想而知 ION 是作爲 MISC 設備註冊進系統的。從這點還可以看出來,用戶空間使用 ION 時必定需要使用 open 啊,ioctl 啊系統調用。事實也正是如此。
  • heaps 成員,在 M86 使用過的 KERNEL LINUX 3.10 中是 struct plist_head 類型,但是在此之前並不是此類型,例如在 M65 使用過的 KERNEL LINUX 3.4 中是 struct rb_root 類型。可見隨着 KERNEL 和 ION 的演進,struct ion_device 的實現會有所改變。本字段管理的是屬於本 struct ion_device 的所有 struct ion_heap 實例。
  • clients 成員,是 struct rb_root 類型,struct rb_root 是紅黑樹,屬於二叉樹的一種。本字段管理的是 struct ion_client 實例。

ion_client

struct ion_client 是由 ion_client_create() [ion.c] 創建,在創建時必須指定上文提到的 struct ion_device 實例。

struct ion_client {
    struct rb_node node;
    struct ion_device *dev;
    struct rb_root handles;
    struct idr idr;
    struct mutex lock;
    const char *name;
    char *display_name;
    int display_serial;
    struct task_struct *task;
    pid_t pid;
    struct dentry *debug_root;
};
  • node 成員,是 struct rb_node 結構類型,用於將本 struct ion_client 實例加入到 struct ion_device,具體的是 struct ion_device::clients。
  • device 成員,是 struct ion_device 指針結構類型,指向所屬的 struct ion_device 實例。
  • handles 成員,是 struct rb_root 結構類型,管理其所擁有的 handle,即 struct ion_handle 實例。一個 struct ion_handle 實例表示一個 buffer,即 struct ion_buffer 實例。而 struct ion_buffer 就是從 heap,即 struct ion_heap 中分配的內存。

ion_heap

struct ion_heap 表示 ION 中的重要概念 heap。系統會通過鏈表或者紅黑樹,這取決與你所使用的 KERNEL 版本,來管理所有的 heap,這些 heap 可用 struct ion_device::heaps 字段來尋找。

struct ion_heap {
    struct plist_node node;
    struct ion_device *dev;
    enum ion_heap_type type;
    struct ion_heap_ops *ops;
    unsigned long flags;
    unsigned int id;
    const char *name;
    struct shrinker shrinker;
    struct list_head free_list;
    size_t free_list_size;
    spinlock_t free_lock;
    wait_queue_head_t waitqueue;
    struct task_struct *task;

    int (*debug_show)(struct ion_heap *heap, struct seq_file *, void *);
};
  • node 成員,是 struct plist_node 結構,用於將本 heap 實例加入到 struct ion_device 所管理的鏈表中,詳情可以參見 ion_device_add_heap() [ion.c] 函數。
  • dev 成員,是 struct ion_device 結構體指針類型,用於指示本 heap 掛在哪一個 struct ion_device 實例下了。
  • type 成員,是 enum ion_heap_type 類型,用於表示本 heap 屬於哪種類型。用戶在使用 ION 分配內存時需要指定 heap 的種類。關於本字段,後續會結合使用方法進行更詳細的介紹。
  • ops 成員,是 struct ion_heap_ops 類型,它很重要!本字段提供的回調函數是用於從本 heap 中分配內存時所時用的。請參見 ion_buffer_create() [ion.c],它會調用 struct ion_heap_ops::allocate() 等回調函數。
    struct ion_heap_ops {
        int (*allocate)(struct ion_heap *heap,
                struct ion_buffer *buffer, unsigned long len,
                unsigned long align, unsigned long flags);
        void (*free)(struct ion_buffer *buffer);
        int (*phys)(struct ion_heap *heap, struct ion_buffer *buffer,
                ion_phys_addr_t *addr, size_t *len);
        struct sg_table * (*map_dma)(struct ion_heap *heap,
                         struct ion_buffer *buffer);
        void (*unmap_dma)(struct ion_heap *heap, struct ion_buffer *buffer);
        void * (*map_kernel)(struct ion_heap *heap, struct ion_buffer *buffer);
        void (*unmap_kernel)(struct ion_heap *heap, struct ion_buffer *buffer);
        int (*map_user)(struct ion_heap *mapper, struct ion_buffer *buffer,
                struct vm_area_struct *vma);
        int (*shrink)(struct ion_heap *heap, gfp_t gfp_mask, int nr_to_scan);
        void (*preload) (struct ion_heap *heap, unsigned int count,
                 unsigned int flags, struct ion_preload_object obj[]);
    };
  • id 成員,也很重要,它可表示優先級,在分配內存時選擇哪一個 heap 有關,必須唯一。

ion_handle

struct ion_handle 其實就是表示 buffer,用戶空間常用它來表示 buffer。本結構通過 ion_handle_create() [ion.c] 分配、初始化。

struct ion_handle {
    struct kref ref;
    struct ion_client *client;
    struct ion_buffer *buffer;
    struct rb_node node;
    unsigned int kmap_cnt;
    int id;
};
  • ref 成員,是 struct kref 結構類型,它在內核中被廣泛的用來表示引用計數。ion_handle 的創建和銷燬都與其有關,下文會有介紹。
  • client 成員,是 struct ion_client 指針類型,指向其所述的 ion_client 實例。
  • buffer 成員,是 struct ion_buffer 指針類型,指向真正的 buffer 所在,它可以說是 stuct ion_handle 的核心成員了。
    下面就來介紹 struct ion_buffer 結構。在分配內存時,也是先通過 ion_buffer_create() 創建 ion_buffer 實例,然後交給 ion_handle_create() 創建 ion_handle 實例。

ion_buffer

struct ion_buffer 很重要,通過 ION 分配的內存就是通過它表示的。它和上面提到的 ion_handle 的區別主要在於一個是用戶空間使用的,一個是內核空間使用的。即雖然常用的接口函數中使用的是 struct ion_handle,但實際上真正表示內存的其實是 struct ion_buffer。

struct ion_buffer {
    struct kref ref;
    ...
    struct ion_device *dev;
    struct ion_heap *heap;
    unsigned long flags;
    unsigned long private_flags;
    size_t size;
    union {
        void *priv_virt;
        ion_phys_addr_t priv_phys;
    };
    struct mutex lock;
    int kmap_cnt;
    void *vaddr;
    int dmap_cnt;
    struct sg_table *sg_table;
    struct page **pages;
    struct list_head vmas;
    struct list_head iovas;
    ...
};
  • ref 成員,是 struct kref 結構實例,維護了本 ion_buffer 的引用計數。當引用計數爲 0 時會釋放該 buffer,即 struct ion_heap_ops::free 會被調用。分配用 ION_IOC_ALLOC 型 ioctl 系統調用,相應的釋放用 ION_IOC_FREE 型 ioctl 系統調用。
  • size 成員,當然是本 buffer 所表示的空間的大小,用字節表示。
  • priv_virt 成員,是所分配內存的虛擬地址啦,它常與 struct sg_table,或者封裝它的結構,有關。它不是我們在內核中讀寫時所需的內核虛擬地址啦,內核虛擬地址使用 vaddr 成員來表示的。一般而言,物理內存不連續的,使用本字段;否則使用下面的 priv_phys 字段,如 struct ion_heap_ops contig_heap_ops。
  • priv_phys 成員,表示所分配的內存的物理地址。它適用於分配的物理內存是連續的 ion heap。這種連續的物理內存:在將其映射到用戶空間時,即獲取用戶空間虛擬地址,可以使用 remap_pfn_range() [memory.c] 這個方便的接口;在將其映射到內核空間時,即獲取內核虛擬地址,可以使用 vmap() [vmalloc.c] 這個方便的接口。例子詳見 struct ion_heap_ops contig_heap_ops [exynos_ion.c]。priv_virt 成員和 priv_phys 成員組成了一個聯合體,其實都表示地址,只不過不同的場景下具體用的不一樣而已。
  • kmap_cnt 成員,記錄本 buffer 被映射到內核空間的次數。
  • vaddr 成員,是本 buffer 對應的內核虛擬地址。當 kmap_cnt 不爲 0 時有效。可以通過 ion_map_kernel() [ion.c] 來獲取本 buffer 對應的內核虛擬地址。ion_map_kernel() [ion.c] 實際上調用的是相應 struct ion_heap_ops::map_kernel 回調函數獲取相應的虛擬地址的。
  • dmap_cnt 成員,記錄本 buffer 被 mapped for DMA 的次數。
  • sg_table 成員,是 struct sg_table 結構體類型的指針。本字段與 DMA 操作有關,而且僅僅在 dmap_cnt 成員變量不爲 0 時是有效的。可以通過 ion_buffer_create() [ion.c] 來初始化本成員變量,該函數實際上是調用相應 ion_heap 所屬的 struct ion_heap_ops::map_dma 回調函數獲取本字段的值的。
  • dirty 成員,表示 bitmask。即以位圖表示本 buffer 的哪一個 page 是 dirty 的,即不能直接用於 DMA。dirty 表示 DMA 的不一致性,即 CPU 緩存中的內容與內存中的實際內容不一樣。

事實上,ION 涉及到的數據結構還有很多,這裏列舉的都是一些非常重要的。 下圖展示了上文介紹到的數據結構的基本關係。
這裏寫圖片描述

重要函數分析

函數對數據進行處理,完成特定的任務,體現算法的具體實現。

ion_device_create

前面分析 ION 的一些核心數據結構時曾經指出,ION 會註冊進 MISC 設備,這樣用戶空間就可以像使用 MISC 設備一樣使用 ION 進行內存分配了。 先來看 ION 是如何註冊進 MISC 子系統的。

struct ion_device *ion_device_create(long (*custom_ioctl)
                     (struct ion_client *client,
                      unsigned int cmd,
                      unsigned long arg))
{
    struct ion_device *idev;
    int ret;
    /*
    * 分配 struct ion_device 實例
    */
    idev = kzalloc(sizeof(struct ion_device), GFP_KERNEL);
    ...
    /*
    * 如前面所說,dev 成員是 struct miscdevice 結構體類型,
    * 這裏初始化其相關字段,名字指定爲 ”ion”,所以提供
    * 給用戶空間調用的設備節點名爲 /dev/ion。
    * 用戶空間操作該設備節點時,ION 驅動中響應的函數集位於
    * ion_fops
    */
    idev->dev.minor = MISC_DYNAMIC_MINOR;
    idev->dev.name = "ion";
    idev->dev.fops = &ion_fops;
    idev->dev.parent = NULL;
    ret = misc_register(&idev->dev);
    ...
    /*
    * 註冊調試信息接口
    */
    idev->debug_root = debugfs_create_dir("ion", NULL);
    ...
    idev->heaps_debug_root = debugfs_create_dir("heaps", idev->debug_root);
    ...
    idev->clients_debug_root = debugfs_create_dir("clients",
                        idev->debug_root);
    ...

debugfs_done:

    idev->custom_ioctl = custom_ioctl;
    idev->buffers = RB_ROOT;
    mutex_init(&idev->buffer_lock);
    init_rwsem(&idev->lock);
    plist_head_init(&idev->heaps);
    idev->clients = RB_ROOT;
    ...
    /* backup of ion device: assumes there is only one ion device */
    g_idev = idev;
    return idev;
}

本函數最重要的是分配並初始化了核心 struct ion_device 實例,並將其和 MISC 設備結合起來,這樣用戶空間就可以通過 open()、ioctl() 等系統調用使用它了。

ion_open

用戶空間要想使用 ION 進行內存分配,首先必須對設備節點 /dev/ion 進行 open() 系統調用。

static int ion_open(struct inode *inode, struct file *file)
{
    struct miscdevice *miscdev = file->private_data;
    struct ion_device *dev = container_of(miscdev, struct ion_device, dev);
    struct ion_client *client;
    char debug_name[64];
    …
    snprintf(debug_name, 64, "%u", task_pid_nr(current->group_leader));
    /*
    * 創建struct ion_client 實例
    */
    client = ion_client_create(dev, debug_name);
    ...
    file->private_data = client;

    return 0;
}

ion_open() 函數最重要的作用就是創建了 struct ion_client 實例。這樣,後續就可以利用 ioctl 系統調用從其中分配內存了。

ION 系統提供的 ioctl 類型有很多,常用的有 ION_IOC_ALLOC、ION_IOC_FREE、ION_IOC_SHARE 和 ION_IOC_IMPORT 等等。

#define ION_IOC_ALLOC       _IOWR(ION_IOC_MAGIC, 0, struct ion_allocation_data)
#define ION_IOC_FREE        _IOWR(ION_IOC_MAGIC, 1, struct ion_handle_data)
#define ION_IOC_SHARE       _IOWR(ION_IOC_MAGIC, 4, struct ion_fd_data)

下面就抽出幾個典型的進行分析。

ion_alloc

這是當用戶空間執行 ION_IOC_ALLOC 型 ioctl() 系統調用時所執行的。

struct ion_handle *ion_alloc(struct ion_client *client, size_t len,
                 size_t align, unsigned int heap_id_mask,
                 unsigned int flags)
{
    struct ion_handle *handle;
    struct ion_device *dev = client->dev;
    struct ion_buffer *buffer = NULL;
    struct ion_heap *heap;
    int ret;
    ...
    /*
    * len 是用戶空間想分配的內存大小,驅動中會將其進行頁對齊
    */
    len = PAGE_ALIGN(len);
    ...
    down_read(&dev->lock);
    /*
    * 用戶空間會指定其想從哪種 heap 分配內存,ION 驅動
    * 會對其進行檢查並找到最合適的。
    */
    heap_id_mask = ion_parse_heap_id(heap_id_mask, flags);
    /*
    * 從系統所有的 heap 中找到最合適的 heap 並分配內存,
    * 創建 struct ion_buffer 實例。
    */
    plist_for_each_entry(heap, &dev->heaps, node) {
        /* if the caller didn't specify this heap id */
        if (!((1 << heap->id) & heap_id_mask))
            continue;
        buffer = ion_buffer_create(heap, dev, len, align, flags);
        if (!IS_ERR(buffer))
            break;
    }
    up_read(&dev->lock);
    ...
    /*
    * 在對創建的 ion_buffer 實例進行了一系列的 sanity 檢查後
    * 利用其創建 struct ion_handle 實例。
    */
    handle = ion_handle_create(client, buffer);
    ...
    mutex_lock(&client->lock);
    /*
    * 將 struct ion_handle 實例加入到其所屬的 struct ion_client
    */
    ret = ion_handle_add(client, handle);
    mutex_unlock(&client->lock);
    ...
    return handle;
}

ion_free

這是當用戶空間執行 ION_IOC_FREE 型 ioctl() 系統調用時所執行的。

void ion_free(struct ion_client *client, struct ion_handle *handle)
{
    bool valid_handle;

    BUG_ON(client != handle->client);

    mutex_lock(&client->lock);
    valid_handle = ion_handle_validate(client, handle);

    if (!valid_handle) {
        WARN(1, "%s: invalid handle passed to free.\n", __func__);
        mutex_unlock(&client->lock);
        return;
    }
    mutex_unlock(&client->lock);
    ion_handle_put(client, handle);
}

此函數比較簡單,重點就是通過 ion_handle_put() 來對上文提到的 struct ion_handle::ref 這個 reference count 減一,當 ref 減到 0 時,就會調用 ion_handle_destroy() 來銷燬 ion_handle 實例。

從前文的分析可知,用戶空間在利用 ION 分配內存時,需要指定具體的 heap mask,即告知 ION 想從哪種 heap 分配內存。 下面就來介紹下。

HEAP 種類

以下是通過 ION 分配內存時,可能會使用到的 heap mask。

#define ION_HEAP_SYSTEM_MASK            (1 << 0)
#define ION_HEAP_SYSTEM_CONTIG_MASK     (1 << 1)
#define ION_HEAP_EXYNOS_CONTIG_MASK     (1 << 4)
#define ION_HEAP_EXYNOS_MASK            (1 << 5)

以 M65 項目爲例,系統定義了 4 種 heap,見 dev-ion.c。

ION_HEAP_TYPE_SYSTEM

本 heap 的名字爲:”ion_noncontig_heap”。相應的 heap mask 爲 1,即 ION_HEAP_SYSTEM_MASK。 在本 struct ion_heap 上分配內存的操作集是: struct ion_heap_ops system_heap_ops [ion_system_heap.c] 其內存可以從 HIGHMEM 中分配,所以其物理內存可能不連續。 在調試 M65 CAMERA 的 HDR 功能時,一開始發現其寫入速度很慢很慢,經過調查,後來在通過 ION 分配內存時指定 ION_FLAG_CACHED | ION_FLAG_CACHED_NEEDS_SYNC | ION_FLAG_PRESERVE_KMAP 標誌後速度得到明顯提高。

ION_HEAP_TYPE_SYSTEM_CONTIG

本 heap 的名字爲:”ion_contig_heap”,相應的 heap mask 爲 2,即 ION_HEAP_SYSTEM_CONTIG_MASK。 在本 struct ion_heap 上分配內存的操作集是: struct ion_heap_ops kmalloc_ops [ion_system_heap.c] 其內存分配 allocate 回調函數爲:ion_system_contig_heap_allocate() [ion_system_heap.c],該函數很簡單,就是利用 kzalloc() 分配內存。

ION_HEAP_TYPE_EXYNOS

本 heap 的名字爲:”exynos_noncontig_heap”,相應的 heap mask 爲 32,即 ION_HEAP_EXYNOS_MASK。 在本 struct ion_heap 上分配內存的操作集是: struct ion_heap_ops vmheap_ops [exynos_ion.c] 其內存可以從 HIGHMEM 中分配,所以其物理內存不一定連續。詳情可以見其 allocate 回調函數 ion_exynos_heap_allocate() [exynos_ion.c]。

ION_HEAP_TYPE_EXYNOS_CONTIG

本 heap 的名字爲:”exynos_contig_heap”。相應的 heap mask 爲 16,即 ION_HEAP_EXYNOS_CONTIG_MASK。 在本 struct ion_heap 上分配內存的操作集是: struct ion_heap_ops contig_heap_ops [exynos_ion.c] 內存由 CMA 分配,所以可以保證其物理地址是連續的。詳情可以參見其 allocate 回調函數 ion_exynos_contig_heap_allocate() [exynos_ion.c]。 因爲本函數是利用 CMA 分配內存,所以可以推測肯定有地方預留了 CMA 所需的物理內存。分析代碼後可以發現,這個地方就位於 mach-m65.c 文件。 M65 的相機驅動使用了本 ION_HEAP_TYPE_EXYNOS_CONTIG 類型的 heap 來分配內存。 按道理,當用戶空間獲取了 struct ion_handle 實例後,就已經完成了 ION 內存的分配任務。但實際上,爲了在不同的進程間,甚至在用戶空間和內核空間共享這段內存使用,用戶空間還通常需要調用 ION_IOC_SHARE 型ioctl(),獲取 ion buffer 相關的 fd,這就和 dma_buf 子系統聯繫起來了。

int ion_share_dma_buf_fd(struct ion_client *client, struct ion_handle *handle)
{
    struct dma_buf *dmabuf;
    int fd;
    /*
    * 前面已經說過 struct ion_handle 其實就是對 struct ion_buffer
    * 的封裝,這裏利用 struct ion_client 實例和 struct ion_handle 實例
    * 創建了一個 dma_buf。
    */
    dmabuf = ion_share_dma_buf(client, handle);
    ...
    /*
    * 重要,這樣就將 struct ion_buffer 對應的內存轉化
    * 爲文件描述符了。將文件描述符傳遞給其它進程或者
    * 傳給內核空間,其它進程或者內核空間就可以使用其內存了
    */
    fd = dma_buf_fd(dmabuf, O_CLOEXEC);
    ...
    return fd;
}

到這裏,ION 使用過程中涉及到的重要函數都已經介紹完全。當然要完全理解這些函數的細節,需要用戶對 dma_buffer 有一定的瞭解。對 dma_buffer 的介紹不屬於本文的範圍,有興趣的話可以參見我寫的其它相關文檔。

結語

關於 Android ION 內存管理機制的介紹就到這裏。本文先介紹了什麼是 ION,爲什麼要用 ION。ION 是爲了解決內存碎片管理而引入的通用內存管理器,用於取代 PMEM 機制。然後介紹了下 ION 中的重要數據結構,對 struct ion_device、struct ion_client、struct ion_heap、struct ion_handle 和 struct ion_buffer 進行了詳細的介紹,並對它們之間的關係進行了闡述。接着,從使用 ION 的場景出發,介紹了一些重要的函數,例如 ion_alloc(),並以實際的相機開發爲例介紹了系統中各個 ion_heap 的種類和各自的內存特性。本文還對進程之間、內核空間和用戶空間之間的內存共享進行了介紹。

參考資料

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章