学习笔记1-深入理解Android内核设计思想

时间:2024-03-15 22:46:18

4.1 计算机体系结构(Computer Architecture)

4.1.1 冯·诺依曼结构

两个深远影响的观点:

  • 采用二进制,抛弃十进制

  • 程序存储(stored-program)

学习笔记1-深入理解Android内核设计思想

4.1.2 哈佛结构

对冯诺依曼结构的改进与完善,区别在指令与数据并不保存在同一个存储器。

这意味着:

  • 指令与数据可以有不同的的数据宽度;

  • 执行速度更快。

学习笔记1-深入理解Android内核设计思想

计算机结构的基本元素:

  • *处理器(CPU)

  • 内存储器

  • I/O设备

4.2 什么是操作系统

定义:

计算机操作系统是负责管理系统硬件,并为上层应用提供稳定编程接口和人机交互界面的软件集合。

最核心的工作:硬件管理与抽象。

肩负两大重任:

  • 面向下层:管理硬件(CPU、内存、Flash、各种IO设备)

  • 面向上层:一方面,为用户提供可用的人机交互界面;另一方面,负责为第三方程序的研发提供便捷、可靠、高效的API。

操作系统的难点:进程和内存管理、硬件驱动的支持等,这正是Linux的长处所在。

4.3 进程间通信的经典实现

进程间的通信(Inter-process communication,IPC)是指运行在不同进程(不论是否在同一台机器)中的若干线程间的数据交换。

实现方式:消息传递、管道、文件共享、操作系统提供的公共信息机制等等。

4.3.1 共享内存(Shared Memory)

一种常用的IPC机制,优势:共享内存区域,减少数据的复制操作,速度快。

实现步骤:

  • 创建内存共享区

  • 映射内存共享区

  • 访问内存共享区

  • 进程间通信

  • 撤销内存映射区

  • 删除内存共享区

4.3.2 管道(Pipe)

一种常见的IPC方式

  • 分立管道的两边,进行数据的传输通信

  • 管道是单向的

  • 一根管道同时具有“读取”端(read end)和“写入”端(write end)

  • 管道有容量限制

4.3.3 UNIX Domain Socket(UDS)

专门针对单机内的进程间通信,有时称为IPC Socket。

Network Socket是以TCP/IP协议栈为基础,UDS因为是本地内的“安全可靠操作”,实现机制上并不依赖于这些协议。

典型流程:

学习笔记1-深入理解Android内核设计思想

UDS的基本流程与传统Socket一致,只是在参数上有区分:

  • 服务器端监听IPC请求;

  • 客户端发起IPC申请;

  • 双方成功建立起IPC连接;

  • 客户端向服务端发送数据,证明IPC通信是有效的

4.3.4 Remote Procedure Calls(RPC)

涉及的通信双方通常运行于两台不同的机器中。

学习笔记1-深入理解Android内核设计思想

4.4 同步机制的经典实现

同步:如果多个进程间存在时序关系,需要协同工作以完成一项任务

互斥:如果多个进程并不满足协同的条件,而只是因为共享具有排他性的资源时所产生的关系。

4.4.1 信号量(Semaphore)

涉及的元素:信号量(S)、PV原语操作(有时称wait()、signal())。

S:共享资源的可用数量

P: 减少S的计数(进入共享区的操作)

V:增加S的计数(退出共享区的操作)

学习笔记1-深入理解Android内核设计思想

4.4.2 Mutex

是Mutual Exclusion的缩写,即互斥体

4.4.3 管程(Monitor)

一种控制更为简单的同步手段。

可以被多个进程/线程安全访问的对象(object)或模块(module)。

具备安全性、互斥性、共享性。

4.4.4 Linux Futex

核心优势是Fast。

4.4.5 同步范例

生产者与消费者问题:

两个进程共享一块大小为N的缓冲区,其中一个进程负责填充数据(生产者),另一个进程负责读取数据(消费者)。

问题的核心有两点:

  • 当缓冲区满时,禁止生产者继续添加数据,直到消费者读取了部分数据;

  • 当缓冲区空时,消费者应等待对方继续生产后再执行操作。

解决方式:信用量,用到3个Semaphore,功能如下:

  • S_emptyCount: 用于生产者获取可用的的缓冲空间大小,初始N

  • S_fillCount: 用于消费者获取可用的数据大小,初始为0

  • S_mutex: 用于操作缓冲区,初始为1

生产者的执行步骤:

  • 循环开始;

  • Produce_item;

  • P(S_emptyCount)

  • P(S_mutex)

  • Put_item_to_buffer

  • V(S_mutex)

  • V(S_fillCount)

  • 继续循环

消费者的执行步骤:

  • 循环开始

  • P(S_fillCount)

  • P(S_mutex)

  • Read_item_from_buffer

  • V(S_mutex)

  • V(S_emptyCount)

  • Consume

  • 继续循环

4.5 Android中的同步机制

4.5.1 进程间同步 - Mutex

头文件:frameworks/native/include/utils/Mutex.h

既可以处理进程内同步、又可以解决进程间同步。

4.5.2 条件判断 - Condition

头文件:frameworks/native/include/utils/Condition.h

核心思想:判断“条件是否已经满足”,如果满足则马上返回,继续执行未完成的动作,否则就进行休眠等待,直到条件满足时有人唤醒它。

4.5.3 “栅栏、障碍” - Barrier

头文件:frameworks/native/services/surfaceflinger/Barrier.h

Barrier是填充了“具体条件”的Condition,专门为SurfaceFlinger而设计。

通常被用于对某线程是否初始化完成进行判断,这种场景具有不可逆性。

4.5.4 加解锁的自动化操作 - Autolock

在Mutex类内部的嵌套类Autolock,实现加、解锁的自动化操作。

当Auto构造时,会主动调用内部成员变量mLock的lock()方法获取一个锁。

而析构时正好相反,调用它的unlock()方法释放锁。

4.5.5 读写锁 - ReaderWriterMutex

基础仍是mutex,特点是 允许有多个对象共享Read锁,但同时却只能有唯一一个对象拥有Write锁。

4.6 操作系统内存管理基础

内存管理是操作系统的重点和难点

内存管理重点理解几个核心:虚拟内存、内存分配与回收、内存保存。

4.6.1 虚拟内存(Virtual Memory)

虚拟内存:为大体积程序的运行提供了可能。

基本思想:

  • 将外存储器的部分空间作为内存的扩展

  • 当内存资源不足时,系统将按照一定算法自动挑选优先级低的数据块,并把它们存储到硬盘中。

  • 后续如果需要用到硬盘中的这些数据块,系统将产生“缺页”指令,然后把它们交换回内存中。

  • 这些操作是由操作系统内部内核自动完成,对上层应用“完全透明”。

涉及3种不同的地址空间:

1、逻辑地址

是程序编译后所产生的地址,Segment Selector(段选择子, 16bit) + Offset (偏移值,32bit)

2、线性地址

是逻辑地址经过分段机制转换后形成的

3、物理地址

是指机器真实的物理内存所能表示的地址空间范围,64KB内存的物理地址范围是 0x0000 - 0xFFFF

学习笔记1-深入理解Android内核设计思想

4.6.2 内存保护(Memory Protection)

4.6.3 内存分配与回收

分Native层(C/C++)、Java层

4.6.4 进程间通信 - mmap

IPC方式: 通过映射同一块物理内存来共享内存,减少数据复制次数,提高效率。

mmap可以将某个设备或者文件映射到应用进程的内存空间中,这样访问这块内存就相当于对设备/文件进行读写,而不需要通过read()、write()。

4.6.5 写时拷贝技术(Copy on Write)

基本思想:多个对象在起始时共享某些资源(如代码段、数据段),直到某个对象需要修改该资源才拥有自己的一份拷贝。

4.7 Android中的Low Memory Killer

Linux底层内核的内存监控机制:OOMKiller,核心思想:按照优先级顺序,从低到高逐步杀掉进程,回收内存。

优先级的设定策略需综合以下几个因素:

  • 进程消耗的内存

  • 进程占用的CPU时间

  • oom_adj(OOM权重)

Android扩展出自己的内存监控体系,Linux的“内存杀手”要等到系统资源“濒临绝境”的情况下才产生效果,而Android则实现了“不同梯级”的Killer(Low Memory Killer(LMK))。

LMK的源码在内核工程的driver/staging/android/Lowmemorykiller.c中。

4.8 Android匿名共享内存(Anonymous Shared Memory)

Anonymous Shared Memory(简称Ashmem)是Android特有的内存共享机制,可以将制定的物理内存分别映射到各个进程自己的虚拟地址空间中,从而便捷地实现进程间的内存共享。

应用实例:基于ashmem设备来实现跨进程内存共享,如MemoryDealer。

匿名共享内存涉及设备驱动、Binder原理等一系列技术,比较难理解,等学习相关基础知识,再来攻克。

4.9 JNI

JNI(Java Native Interface)是一种允许运行于JVM的Java程序去调用(反向亦然)本地代码的编程框架。

有3种情况需要用到JNI:

  • 应用程序需要一些平台相关的feature的支持,而Java无法满足

  • 兼容以前的用其他语言书写的代码库

  • 应用程序的某些关键操作对运行速度要求较高。

JNI涉及以下两方面:

  • Java Code -> Native Code

  • Native Code -> Java Code

4.9.1 Java函数的本地实现

创建一个可供Java代码调用的本地函数步骤:

  • 将需要本地实现的Java方法加上native声明;

  • 使用javac命令编译Java类

  • 使用javah生成.h头文件

  • 在本地代码中实现native方法

  • 编译上述的本地方法,生成动态链接库

  • 在Java类中加载这一动态链接库

  • Java代码中其他地方可以正常调用这个native方法

4.9.2 本地代码访问JVM

上节内容的“逆向”操作,本地层(C/C++)访问JVM空间(Java)。

4.10 Java中的反射机制

4.11 学习Android系统的两条线索

主线:操作系统的体系结构、硬件组成

辅线:在主线的基础上,以Android系统的5层框架为辅,逐一解析各层框架中的重要元素,或拾级而上,或深入浅出,直到问题的最根源处。