4.1 计算机体系结构(Computer Architecture)
4.1.1 冯·诺依曼结构
两个深远影响的观点:
-
采用二进制,抛弃十进制
-
程序存储(stored-program)
4.1.2 哈佛结构
对冯诺依曼结构的改进与完善,区别在指令与数据并不保存在同一个存储器。
这意味着:
-
指令与数据可以有不同的的数据宽度;
-
执行速度更快。
计算机结构的基本元素:
-
中央处理器(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因为是本地内的“安全可靠操作”,实现机制上并不依赖于这些协议。
典型流程:
UDS的基本流程与传统Socket一致,只是在参数上有区分:
-
服务器端监听IPC请求;
-
客户端发起IPC申请;
-
双方成功建立起IPC连接;
-
客户端向服务端发送数据,证明IPC通信是有效的
4.3.4 Remote Procedure Calls(RPC)
涉及的通信双方通常运行于两台不同的机器中。
4.4 同步机制的经典实现
同步:如果多个进程间存在时序关系,需要协同工作以完成一项任务
互斥:如果多个进程并不满足协同的条件,而只是因为共享具有排他性的资源时所产生的关系。
4.4.1 信号量(Semaphore)
涉及的元素:信号量(S)、PV原语操作(有时称wait()、signal())。
S:共享资源的可用数量
P: 减少S的计数(进入共享区的操作)
V:增加S的计数(退出共享区的操作)
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
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层框架为辅,逐一解析各层框架中的重要元素,或拾级而上,或深入浅出,直到问题的最根源处。