Android包含一个常备的Linux内核,提供标准的Linux功能特性。Android作为一个操作系统,在某些方面做了重要拓展,以适应移动设备的特性。
1. 唤醒锁
在传统的计算机系统上,系统可以处于两种电源状态之一:运行并且准备处理用户输入,或者深度睡眠(如果没有诸如按下电源键一类的外部中断不能继续执行)。
在运行的时候,次要的硬件设备可以按需要通电或者断电,但是CPU本身以及核心硬件设备部件必须保持通电状态以处理到来的网络通信以及其他类似的事件。进入低能耗睡眠状态是发生得比较少的事情,一般来说通过用户明确的操作或者由于长时间的未活动而进入睡眠。从这样的睡眠状态醒来需要来自外部源的硬件中断,例如按下键盘上的按键,在此刻设备将醒来并且点亮屏幕。
移动设备的用户具有不同的期望,尽管用户可以关闭屏幕,在这样的情况下看起来好像是让设备睡眠了,但是传统的睡眠状态实际上并不是用户想要的。当设备的屏幕关闭时,设备仍然需要工作,它需要能够接听电话呼叫,接收并处理到来的聊天消息数据,以及许多其它事情。对于移动设备,关于打开和关闭设备屏幕的期望同样比传统的计算机具有更高的要求。移动交互趋向于在一整天中有许多次短时的突发:你收到一条消息并且打开设备查看....。在这类典型的应用场景中,恢复设备直到它能使用的任何延迟都会对用户体验造成严重的负面影响。
给定了这样的需求,一种解决方案或许仅仅是当设备的屏幕关闭之时不让CPU睡眠,这样它就总是准备好再次重新打开。归根到底,内核了解什么时候线程无需工作调度,并且Linux将会自动的让CPU空闲,在这样的情况下使用较低的电能。然而,空闲的CPU与真正的睡眠是不同的,例如:
1)在许多芯片组上,空闲状态使用的电能比真正的睡眠状态要多得多;
2)空闲的CPU可以在任何时刻唤醒,只要某些工作碰巧变得可用,即使该工作不重要。
3)CPU空闲并不意味着可以关闭其他硬件,而这样的硬件在真正的睡眠中是不需要的。
Android上的唤醒锁(wake lock)允许系统进入深度睡眠模式,而不必与一个明确的用户活动(例如关闭屏幕)绑在一切。当设备在运行时,为了保持它不回到睡眠,则需要持有一个唤醒锁。例如当屏幕打开时,系统总是持有一个唤醒锁,这样就阻止了设备进入睡眠。一旦系统已经进入睡眠,硬件中断可以将其再次唤醒,这样的中断源有基于时间的报警、来自蜂窝无线电的事件(例如呼入的呼叫)、到来的网络通信以及按下特定的硬件按钮(例如电源按钮)。
针对这些事件的中断程序要求对标准Linux做出一个改变:在处理完中断之后,它们需要获得一个初始的唤醒锁从而使系统保持运行。中断处理程序获得的唤醒锁必须持有足够长的时间,以便能够沿着栈向上将控制传递给内核中的驱动程序,由其继续对事件进行处理。然后,内核驱动程序负责获得自己的唤醒锁,在此之后,中断唤醒锁可以安全的得到释放而不存在系统进入睡眠的风险。
如果在这之后驱动程序将该事件向上传送到用户空间,则需要类似的握手。驱动程序必须确保继续持有唤醒锁直到它将事件传递给等待的用户进程,并且要确保存在使用用户进程获得自己的唤醒锁的条件。这一流程可能还会在用户空间的子系统之间继续,只要某个实体持有唤醒锁,我们就继续执行想要的处理以便响应事件。然而一旦没有唤醒锁被持有,整个系统将返回睡眠并且所有进程停止。
2. 内存不足杀手
Linux中的“内存不足杀手”(out of memory killer)试图在内存极低时进行恢复。在低内存的情况下,Linux系统试图找到RAM,使得内核能够继续处理它正在做的事情。做法是为每个进程分配一个“坏度(badness)”水平,并且简单地杀死最坏的进程。进程的坏度基于进程正在使用的RAM数量、它已经运行了多长时间以及其他因素,目标是杀死大量但不太重要的进程。
Android为内存不足杀手是施加了特别的压力。它没有交换空间,所以它处于内存不足的情形更为常见,除非通过放弃从最近使用的存储器映射的干净的RAM页面,否则没有办法缓解内存压力。Android使用Linux标准的配置,进行过度提交(over commit)内存,即允许在RAM中分配地址空间而无需保证有可用的RAM对其提供后备。过度提交对于优化内存使用是一个极其重要的工具,这是因为mmap大文件是很常见的,此处只需要将该文件中全部数据的一小部分装入RAM。
与试图猜测哪个进程应该被杀死不同,Android的内存不足杀手非常严格的依赖由用户空间提供给它的信息。传统的Linux内存不足杀手具有每个进程的oom_adj参数,通过修改进程的总体坏度得分,该参数可用来指导选择最佳的进程并将其杀死。Android的内存不足杀手使用相同的参数,但是具有严格的顺序:具有较高oom_adj的进程总是在那些较低omm_adj的进程之前被杀死。
参考资料:
[1] 现代操作系统
网友评论