深入理解 Android 内核设计思想(二)内存管理,安卓插件化开发

操作系统内存管理基础

不论什么操作系统,内存管理都是绝对的重点和难点。内存管理旨在为系统中所有 Task 提供稳定可靠的内存分配、释放和保护机制。你可能会疑问,学习 Android 系统有必要了解 Linux Kernel 的内存管理机制吗?

是的!不论是 Android 的音频系统、GUI 系统,还是 Binder 的实现机理等,都是和内存管理息息相关的。

虚拟内存

虚拟内存就是当内存资源不足时,借用硬盘中的一部分的空间,充当内存使用。系统会挑选优先级低的内存数据放入硬盘,后续若要用到硬盘中的数据,系统会产生一次缺页中断,然后把数据交换回内存中。

要理解虚拟内存机制,就要理解三种地址空间,分别是逻辑地址、线性地址和物理地址:

1.逻辑地址(Logical Address)

逻辑地址是程序编译后产生的地址,也称为相对地址,由两部分组成:

段选择子(Segment Selector):描述逻辑地址所处的段

Offset:描述所在段内的偏移值

2.线性地址(Linear Address)

线性地址是由逻辑地址经过分段机制转换后得到的。

大致转换过程为:通过段选择子确定段的基地址,然后结合 Offset 得到线性地址。

3.物理地址(Physical Address)

物理地址就是指机器真实的物理内存地址,任何操作系统,最终都要通过物理地址来访问内存。若系统开启了分页机制,则在得到线性地址后需要通过分页机制转换后,才能得到物理地址。

简单来说,由逻辑地址得到物理地址过程如下:

逻辑地址 -> 分段机制转换 -> 线性地址 -> 分页机制转换 -> 物理地址

内存分配与回收

内存的分配与回收是操作系统的重要组成部分,需要解决的核心问题包括:

1.操作系统应保证应用程序的硬件无关性,硬件差异不能体现在应用程序上

2.内存划分的区域、分配粒度、最小单位,管理区分已使用和未使用的内存,回收等等

3.优化内存碎片,考虑整体机制的高效性

mmap

mmap(Memory Map) 可以将某个设备或文件映射到应用进程的内存空间中,这样应用程序访问这块内存,相当于直接对设备/文件读写,不再需要 read、write 等 IO 操作。

mmap 函数如下:

//映射成功返回0,否则返回错误码

void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);

  • addr:指文件/设备应该映射到进程空间的哪个起始地址

  • **len:**指被映射到进程空间的内存块大小

  • **prot:**指定被映射内存的访问权限,包括 PROT_READ(可读)、PROT_WRITE(可写) 等

  • **flags:**指定程序对内存块所做改变造成的影响,包括 MAP_SHARED(保存到文件) 等

  • **fd:**被映射到进程空间的文件描述符

  • **offset:**指定从文件的哪一部分开始映射

mmap 可用于跨进程通信,Linux Kernel 和 Android 中就频繁的用到了这个函数,比如 Android 的 Binder 驱动,下面分析 MemoryFile 原理时还会提到这个函数。

Copy on Write

Copy on Write(写时拷贝) 是指如果有多个调用者要请求同一资源,他们会获取到相同的指向这一资源的指针,直到某个调用者需修改资源时,系统才会复制一份副本给该调用者,而其他调用者仍使用最初的资源。

如果调用者不需要修改资源,就不会建立副本,多个调用者共享读取同一份资源。

Linux 的 fork() 函数就是 Copy on Write 的,实际开销很小,主要是给子进程创建进程描述符等,并且推迟甚至免除了数据拷贝操作。比如 fork() 后子进程需立即调用 exec() 装载新程序到进程的内存空间,即不需要父进程的任何数据,这种情况 Copy on Write 技术就避免了不必要的数据拷贝,从而提升了运行速度。

Android 内存管理


Low Memory Killer

Linux Kernel 有自己的内存监控机制,即 OOMKiller。当系统的可用内存达到临界值时,OOMKiller 就会按照优先级从低到高杀掉进程。优先级该如何衡量呢?OOMKiller 会综合进程当前消耗内存、进程占用 CPU 时间、进程类型等因素,对进程实时评分。分值存储在 /proc/{PID}/oom_score 中,可通过 cat 命令查看。分值越低的进程,优先级越高,被杀死的概率越小。

基于 Linux 内核 OOMKiller 的核心思想,Android 系统拓展出了自己的内存监控体系,相比 Linux 达到临界值才触发,Android 实现了不同梯级的 Killer。Android 系统为此开发了专门的驱动,名为 Low Memory Killer,源码在内核的 /drivers/staging/android/Lowmemorykiller.c 中。

Lowmemorykiller.c 中有如下定义:

static int lowmem_adj[6] = {0, 1, 6, 12};

static int lowmem_adj_size = 4; //页大小

static size_t lowmem_minfree[6] = { //元素使用时以 lowmem_adj_size 为单位

3 * 512, //6MB

2 * 1024, //8MB

4 * 1024, //16MB

16 * 1024,//64MB

};

lowmem_minfree 定义了可用内存容量对应的不同梯级。lowmem_adj 与 lowmem_minfree 中的梯级一一对应,表示处于某梯级时需要被处理的 adj 值。adj 值用来描述进程的优先级,取值范围为 -17~15,数字越小表示进程优先级越高,被杀死的概率越小。

比如当可用内存低于 64MB 时,即 lowmem_minfree 第 4 梯级,对应于 lowmem_adj 的 12,那就会清理掉优先级低于 12(即 adj>12)的进程。

上面这两个数组中梯级的定义只是系统的预定义值,Android 系统还提供了相应的文件供我们修改这两组值,路径为:

/sys/module/lowmemorykiller/parameters/adj

/sys/module/lowmemorykiller/parameters/minfree

可以在 init.rc(系统启动时由 init 进程解析的一个脚本) 中,这样修改:

write /sys/module/lowmemorykiller/parameters/adj 0, 8

write /sys/module/lowmemorykiller/parameters/minfree 1024, 4096

另外 ActivityManagerService 中有一个 updateOomLevels 方法也是通过修改这两个文件来实现的,AMS 在运行时会根据当前的系统配置自动调整 adj 和 minfree,以尽可能适配不同的硬件设备。

了解了 Low Memory Killer 的梯级规则后,来看下 Android 进程的 adj 值含义:

除了表格中系统的评定标准,有没有办法改变某一进程的 adj 值呢?和修改上面的 adj、minfree 梯级类似,进程的 adj 值也可以通过写文件的方式来修改,路径为 /proc/{PID}/oom_adj,比如 init.rc 中:

write /proc/1/oom_adj -16

另外还可以在 AndroidManifest.xml 中给 application 添加 “android:persistent=true” 属性。

Ashmem 驱动

Anonymous Shared Memory 匿名共享内存是 Android 特有的内存共享机制,它可以将指定的物理内存分别映射到各个进程自己的虚拟地址空间中,从而便捷的实现进程间内存共享,Ashmem 的实现依赖 Ashmem 设备节点。

怎么理解设备节点呢?Linux 抽象了对硬件的处理,所有的硬件设备都可以当作普通文件一样来看待,设备节点文件是设备驱动的逻辑文件,其中对设备的描述包括文件操作函数集合,应用程序可以通过这些函数来访问硬件设备。

除了磁盘等真正的硬件设备,还可以通过内存抽象,使用设备节点文件的方式来描述一个”设备”并使用它,Ashmem、Binder 驱动都是属于这种内存抽象的”设备”。

介绍 Ashmem 设备节点前,先了解下 ueventd 进程。ueventd 就是 Android 中负责创建和管理设备节点的进程,创建设备节点文件有两种方式:

1.静态节点文件:以预先定义的设备信息为基础,当 ueventd 进程启动后,统一创建设备节点文件

2.动态节点文件:即在系统运行中,当有设备插入 USB 端口时,ueventd 进程就会接收到这一事件,为插入的设备动态创建设备节点文件

Ashmem 设备节点就属于静态节点文件,创建过程如下:

1.Android 系统启动,解析 init.rc,启动 ueventd 进程

2.ueventd 进程会去解析 ueventd.rc,读取 ashmem 设备节点信息到系统中

其中 ueventd.rc 文件格式如下:

/dev/null 0666 root root

/dev/zero 0666 root root

/dev/random 0666 root root

/dev/ashmem 0666 root root

/dev/binder 0666 root root

可以看到包括 binder、ashmem 在内的一系列设备节点信息都会在这里读取到系统中。

随后 ashmem 会调用 ashmem.c 文件的 ashmem_init 进行初始化:

static int _init ashmem_init(void){

int ret;

ashmem_area_cachep = kmem_cache_create(“ashmem_area_cache”,sizeof(struct ashmem_area),0,0,NULL);

ashmem_range_cachep = kmem_cache_create(“ashmem_range_cache”,sizeof(struct ashmem_range),0,0,NULL);

ret = misc_register(&ashmem_misc);

return 0;

}

通过 kmem_cache_create() 函数创建了两个 cache,后面申请内存时需要用到。对于 kmem_cache_create() 函数,书中提及 Slab、Slub、Slob 三种机制,这里不再延伸,仅理解:kmem_cache_create() 并没有真正的分配内存,后续还要调用 kmem_cache_alloc() 。

由于 ashmem 属于 misc 杂项设备,所以调用 misc_register(&ashmem_misc) 进行设备注册。ashmem_misc 就是 Ashmem 的设备描述,定义如下:

static struct miscdevice ashmem_misc = {

.minor = MISC_DYNAMIC_MINOR, //自动分配次设备号

.name = “ashmem”, //设备节点的名称

.fops = &ashmem_fops, //文件操作集合

};

.fops 就是上面提到的”文件操作函数集合”,即 Ashmem 设备的操作函数集,如下

static 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,

.compat_ioctl = ashmem_ioctl,

};

其中 ashmem_open、ashmem_mmap 及 ashmem_ioctl 函数比较重要,依次来看:

1.ashmem_open

static int ashmem_open(struct inode *inode, struct file *file){

struct ashmem_area *asma;

asma = kmem_cache_zalloc(ashmem_area_cachep, GFP_KERNEL);

file->private_data = asma;

return 0; //申请成功

}

ashmem_open 主要做了两个工作:

1.调用 kmem_cache_zalloc 方法从 ashmem_area_cachep 分配了一块内存,这个方法和 cache 上面都提到过

2.将 ashmem_area 记录在 file 中 。

2.ashmem_mmap

static int ashmem_mmap(struct file *file, struct vm_area_struct *vma){

struct ashmem_area *asma = file->private_data;

mutex_lock(&ashmem_mutex);

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

最后送福利了,现在关注我可以获取包含源码解析,自定义View,动画实现,架构分享等。
内容难度适中,篇幅精炼,每天只需花上十几分钟阅读即可。
大家可以跟我一起探讨,有flutter—底层开发—性能优化—移动架构—资深UI工程师 —NDK相关专业人员和视频教学资料,还有更多面试题等你来拿

录播视频图.png

0fd6ac81d625c.png)

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-GDvdwaBP-1711878583997)]

最后送福利了,现在关注我可以获取包含源码解析,自定义View,动画实现,架构分享等。
内容难度适中,篇幅精炼,每天只需花上十几分钟阅读即可。
大家可以跟我一起探讨,有flutter—底层开发—性能优化—移动架构—资深UI工程师 —NDK相关专业人员和视频教学资料,还有更多面试题等你来拿

[外链图片转存中…(img-4L0PIGJZ-1711878583997)]

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

  • 20
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值