1、吞吐量和响应
吞吐:全局视野,整个系统的workload被最大化处理。以特定时间单位看操作系统,他把时间都花在有用功上进行解决系统的负载,比如系统有很多事情做,吞吐就是把时间花在有意义的事情上;
响应:最小化某个任务的响应时间,哪怕牺牲其他的任务为代价。比如点击鼠标,就必须及时响应。
在某些实时操作系统中,强调一点:高优先级任务一ready就去抢占低优先级的任务,抢占会导致吞吐就会下降,因为我们说吞吐是把时间花在有用功上面,而不是抢占任务这些事情上(因为你花时间在上下文切换了)。
上下文切换计算方式:上下文切换不仅关注切换所花费的时间,还需要关注上下文切换会引起cache miss,比如你执行微博时候 cpu的高速缓存会保存着微博的代码和数据,但是如果你一切换到微信,那么cpu的高速缓存很难命中到微信,那么微信中的东西需要重新load一次,我们知道内存比cpu速度慢很多,所以切换上下文的时候 除了计算切换这些时间,还需要关注cache miss等;
操作系统会对吞吐和响应做了一个权衡。
2、进程和CPU相关概念
- 进程:进程就是正在进行中的程序。从用户角度来看,进程是程序的一次动态执行过程。从操作系统角度来看,进程是操作系统分配资源的基本单位,也是最小实体。
- CPU负载(cpu load)指的是某个时间点进程对系统产生的压力
- 进程的特征
动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的。
并发性:任何进程都可以同其他进程一起并发执行。
独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位。
异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进。
结构性:进程由程序段,数据段和PCB组成 - 进程的三种状态
就绪 → 执行:为就绪线程分配CPU即可变为执行状态"
执行 → 就绪:正在执行的线程由于时间片用完被剥夺CPU暂停执行,就变为就绪状态
执行 → 阻塞:由于发生某事件,使正在执行的线程受阻,无法执行,则由执行变为阻塞
3、Linux进程调度算法
linux调度算法一开始是2.6版本,后续增加了两个补丁来完善改进调度算法。
调度器早期2.6 把linux内核空间的进程优先级化为0-139之间,优先级数值越小优先级越高。0-99优先级的进程调度使用的是RT策略,100-139优先级的进程调度是非RT策略的。
0-99的RT策略分为两种:
SCHED_FIFO :不同优先级按照优先级高先跑到睡眠,优先级低的再跑,同等优先级先进先出。
SCHED_RR:不同优先级按照优先级高先跑到睡眠,优先级低的再跑,同等优先级轮转。
当全部的0-99的进程均跑到睡眠状态,操作系统就会调度到优先级在100-139的普通进程进行跑。
普通进程是按照nice进行调度的,nice值越高优先级最低。nice值计算 = 优先级 - 120 。比如优先级为100的进程,其nice值为-20,而优先级为139的进程其nice值为+19;
对于普通进程,不会因为你优先值高而可以一直独占cpu资源,而是会进行轮转。那么优先级对于普通进程有啥作用呢?主要有两点作用:
1、轮转时候获取更多时间片,
2、在刚刚醒来的时刻可以抢占优先级低的进程。nice值越高优先级最低。
当我们在linux中给进程设置一个静态nice值时候,但是我们知道linux要优先调度IO型进程,我们可以在启动进程时候设置nice值,在2.6版本中linux会在进程运行时候根据进程的睡眠/计算等情况,来对进程进行奖励和惩罚。linux会探测你是喜欢等待还是喜欢计算,越睡优先级越高。(希望IO类型进程被优先调度到)
为什么linux2.6要这样做呢?这样做的目的是希望io消耗性可以早点被调度到,但是这种模式有点傻。linux后期做了两个变更。
2个补丁
1、rt的门限
在period的时间里RT(0-99的优先级进程)最多只能 跑runtime的时间:
/proc/sys/kernel/sched_rt_period_us 比如说是1000
/proc/sys/kernel/sched_rt_runtime_us 比如说是 950
这样可以限制RT(0-99优先级)在1000ms内最多可以跑950ms,剩下的时间必须让100-139的普通进程进行运行;
2、CFS公平调度算法
linux针对普通进程提出了cfs算法。完全公平调度。要照顾到普通进程的完全公平。用到的数据结构是红黑树:红黑树,左边节点小于右边节点的值。
运行到目前为止vruntime最小的进程,同时考虑了CPU/I0和nice。
- 这个算法完全照顾了nice值低的/io消耗型进程等;
- 照顾了io消耗型进程:因为io消耗性喜欢谁,所以phyruntime值小,那么整体vruntime值小就容易比调度到;
- 照顾了nice值低的进程:nice值低,权重越高,那么vruntime就越低,就约容易被调度到;
4、CPU/IO消耗型进程
进程类型分为cpu和io消耗型。
cpu消耗型:多数时间花在cpu上做运算;
IO消耗性:cpu利用率低,进程运行效率受限于I/O速度;
举例IO消耗型进程 花费1ms在cpu上跑下指令,花费100ms等到资源;对于这种类型的进程,如果CPU好,花费1ms,如果cpu差2ms,总任务耗时101ms和102ms,因此CPU的好坏对于IO消耗型进程不是很关键,但是这类进程很在乎是不是被及时调度到。比如等待了很久才被调度到 那么整体耗时是线性增长的。
总结下:IO消耗型的进程比较在乎是否及时拿到cpu,而不在乎拿到的cpu是不是很好。在调度算法中一般IO消耗性的调度优先级都是高于CPU消耗型的;
一般手机等操作系统,会提供4个比较好的cpu内核和4个big.LiTTLE内核。4个比较好的内核是用于服务cpu进程,而4个比较差点的内核主要用户io消耗型进程。(这样系统其实只花了4个好内核+4个big.LiTTLE内核的钱 但是可以提供解决8内核的服务)
5、进程的重要性,划分5级
- 前台进程(Foreground process)
- 可见进程(Visible process)
- 服务进程(Service process)
- 后台进程(Background process)
- 空进程(Empty process)
前台进程的重要性最高,依次递减,空进程的重要性最低,下面分别来阐述每种级别的进程
1、 Foreground process
用户当前操作所必需的进程。通常在任意给定时间前台进程都为数不多。只有在内存不足以支持它们同时继续运行这一万不得已的情况下,系统才会终止它们。
- 拥有用户正在交互的 Activity(已调用onResume())
- 拥有某个 Service,后者绑定到用户正在交互的 Activity
- 拥有正在“前台”运行的 Service(服务已调用 startForeground())
- 拥有正执行一个生命周期回调的 Service(onCreate()、onStart() 或 onDestroy())
- 拥有正执行其 onReceive() 方法的 BroadcastReceiver
2 、Visible process
没有任何前台组件、但仍会影响用户在屏幕上所见内容的进程。可见进程被视为是极其重要的进程,除非为了维持所有前台进程同时运行而必须终止,否则系统不会终止这些进程。
- 拥有不在前台、但仍对用户可见的 Activity(已调用onPause())。
- 拥有绑定到可见(或前台)Activity 的 Service
3 、Service process
尽管服务进程与用户所见内容没有直接关联,但是它们通常在执行一些用户关心的操作(例如,在后台播放音乐或从网络下载数据)。因此,除非内存不足以维持所有前台进程和可见进程同时运行,否则系统会让服务进程保持运行状态。
正在运行startService()方法启动的服务,且不属于上述两个更高类别进程的进程。
4 、Background process
后台进程对用户体验没有直接影响,系统可能随时终止它们,以回收内存供前台进程、可见进程或服务进程使用。 通常会有很多后台进程在运行,因此它们会保存在LRU列表中,以确保包含用户最近查看的Activity的进程最后一个被终止。如果某个 Activity 正确实现了生命周期方法,并保存了其当前状态,则终止其进程不会对用户体验产生明显影响,因为当用户导航回该 Activity 时,Activity 会恢复其所有可见状态。
对用户不可见的Activity的进程(已调用Activity的onStop()方法)
5 、Empty process
保留这种进程的的唯一目的是用作缓存,以缩短下次在其中运行组件所需的启动时间。 为使总体系统资源在进程缓存和底层内核缓存之间保持平衡,系统往往会终止这些进程。
6、配置 CPUset
使用 CPUset 子系统可以限制某一类的任务跑在特定的 CPU 或者 CPU 组里面,比如下面,Android 中会划分一些默认的 CPU 组,厂商可以针对不同的 CPU 架构进行定制,目前默认划分
- system-background 一些低优先级的任务会被划分到这里,只能跑到小核心里面
- foreground 前台进程
- top-app 目前正在前台和用户交互的进程
- background 后台进程
- foreground/boost 前台 boost 进程,通常是用来联动的,现在已经没有用到了,之前的时候是应用启动的时候,会把所有 foreground 里面的进程都迁移到这个进程组里面
在 OomAdjuster 中会动态根据进程的状态修改其对应的 CPUset 组, 详细可以自行查看 OomAdjuster 中 computeOomAdjLocked、updateOomAdjLocked、applyOomAdjLocked 的执行逻辑(Android 10)
7、内存
页面回收,并不是回收得越多越好,而是力求达到一种balanced。 因为页面回收总是以cache丢弃、内存swap等为代价的,对系统 性能会有一定程度的影响。而balanced,就是既要保证性能,又要应付好新来的页面分配请求。而kswapd也是以此为原则进行内存的回收。
Linux内核内存管理的一项重要工作就是如何在频繁申请释放内存的情况下,避免碎片的产生。Linux采用伙伴系统解决外部碎片的问题,采用slab解决内部碎片的问题,在这里我们先讨论外部碎片问题。避免外部碎片的方法有两种:一种是之前介绍过的利用非连续内存的分配;另外一种则是用一种有效的方法来监视内存,保证在内核只要申请一小块内存的情况下,不会从大块的连续空闲内存中截取一段过来,从而保证了大块内存的连续性和完整性。显然,前者不能成为解决问题的普遍方法,一来用来映射非连续内存线性地址空间有限,二来每次映射都要改写内核的页表,进而就要刷新TLB,这使得分配的速度大打折扣,这对于要频繁申请内存的内核显然是无法忍受的。因此Linux采用后者来解决外部碎片的问题,也就是著名的伙伴系统。
Android中对于内存的回收,主要依靠Lowmemorykiller来完成,是一种根据阈值级别触发相应力度的内存回收的机制。
ADJ级别
定义在ProcessList.java文件,oom_adj划分为16级,从-17到16之间取值。
**ADJ级别**
取值
解释
UNKNOWN_ADJ
16 一般指将要会缓存进程,无法获取确定值
CACHED_APP_MAX_ADJ
15 不可见进程的adj最大值 1
CACHED_APP_MIN_ADJ
9 不可见进程的adj最小值 2
SERVICE_B_AD
8 B List中的Service(较老的、使用可能性更小)
PREVIOUS_APP_ADJ
7 上一个App的进程(往往通过按返回键)
HOME_APP_ADJ
6 Home进程
SERVICE_ADJ
5 服务进程(Service process)
HEAVY_WEIGHT_APP_ADJ
4 后台的重量级进程,system/rootdir/init.rc文件中设置
BACKUP_APP_ADJ
3 备份进程 3
PERCEPTIBLE_APP_ADJ
2 可感知进程,比如后台音乐播放 4
VISIBLE_APP_ADJ
1 可见进程(Visible process) 5
FOREGROUND_APP_ADJ
0 前台进程(Foreground process) 6
PERSISTENT_SERVICE_ADJ
-11 关联着系统或persistent进程
PERSISTENT_PROC_ADJ
-12 系统persistent进程,比如telephony
SYSTEM_ADJ
-16 系统进程
NATIVE_ADJ
-17 native进程(不被系统管理)
**进程state级别**
定义在ActivityManager.java文件,process_state划分18类,从-1到16之间取值。
state级别
取值
解释
PROCESS_STATE_CACHED_EMPTY
16 进程处于cached状态,且为空进程
PROCESS_STATE_CACHED_ACTIVITY_CLIENT
15 进程处于cached状态,且为另一个cached进程(内含Activity)的client进程
PROCESS_STATE_CACHED_ACTIVITY
14 进程处于cached状态,且内含Activity
PROCESS_STATE_LAST_ACTIVITY
13 后台进程,且拥有上一次显示的Activity
PROCESS_STATE_HOME
12 后台进程,且拥有home Activity
PROCESS_STATE_RECEIVER
11 后台进程,且正在运行receiver
PROCESS_STATE_SERVICE
10 后台进程,且正在运行service
PROCESS_STATE_HEAVY_WEIGHT
9 后台进程,但无法执行restore,因此尽量避免kill该进程
PROCESS_STATE_BACKUP
8 后台进程,正在运行backup/restore操作
PROCESS_STATE_IMPORTANT_BACKGROUND
7 对用户很重要的进程,用户不可感知其存在
PROCESS_STATE_IMPORTANT_FOREGROUND
6 对用户很重要的进程,用户可感知其存在
PROCESS_STATE_TOP_SLEEPING
5 与PROCESS_STATE_TOP一样,但此时设备正处于休眠状态
PROCESS_STATE_FOREGROUND_SERVICE
4 拥有给一个前台Service
PROCESS_STATE_BOUND_FOREGROUND_SERVICE
3 拥有给一个前台Service,且由系统绑定
PROCESS_STATE_TOP
2 拥有当前用户可见的top Activity
PROCESS_STATE_PERSISTENT_UI
1 persistent系统进程,并正在执行UI操作
PROCESS_STATE_PERSISTENT
0 persistent系统进程
PROCESS_STATE_NONEXISTENT
-1 不存在的进程
lmk策略
Lowmemorykiller根据当前可用内存情况来进行进程释放,总设计了6个级别,即上表中“解释列”加粗的行,即Lowmemorykiller的杀进程的6档,如下:
CACHED_APP_MAX_ADJ
CACHED_APP_MIN_ADJ
BACKUP_APP_ADJ
PERCEPTIBLE_APP_ADJ
VISIBLE_APP_ADJ
FOREGROUND_APP_ADJ
系统内存从很宽裕到不足,Lowmemorykiller也会相应地从CACHED_APP_MAX_ADJ(第1档)开始杀进程,如果内存还不足,那么会杀CACHED_APP_MIN_ADJ(第2档),不断深入,直到满足内存阈值条件。
8、CPU动态调频
- ondemand:按需调节模式,实现了动态频率调节,平时以低速方式运行,当系统负载提高时候自动提高频率。以这种模式运行不会因为降频造成性能降低,同时也能节约电能和降低温度。
- interactive:交互模式,是以 CPU 负载而调整频率,从而实现省电。
- InteractiveX:交互模式,是以 CPU 负载来调整频率,不会过度把频率调低。所以比 Interactive 反应好些,但是省电的效果一般
- conservative:保守模式,类似于ondemand,但调整相对较缓,省电效果很棒。
- smartass:聪明模式,是I和C模式的升级,该模式在比i模式不差的响应的前提下会做到了更加省电。
- performance:性能模式,只有最高频率,性能高,耗电量大。
- powersave 省电模式,通常以最低频率运行,性能低,耗电量小。
- userspace:用户自定义模式,系统将变频策略的决策权交给了用户态应用程序,并提供了相应的接口供用户态应用程序调节CPU 运行频率使用。可以通过手动编辑配置文件进行配置
- Hotplug:类似于ondemand, 但是cpu会在关屏下尝试关掉一个cpu,并且带有deep sleep,比较省电。
负载 - 频率机制
target_loads:负载;这个参数的目的是根据 CPU 负载来调整频率:
当 CPU 负载升高到该参数时,内核就会升高 CPU 的运行频率以便降低 CPU 负载。该参数的默认值为 80。
该参数的格式是单个固定数值,或者是频率和负载值成对出现用冒号隔开。
比如 85 1000000:90 1700000:99 表示负载在 85% 以下时,CPU 频率要运行在 1GHz 以下;
负载达到 90% 时,CPU 频率要运行在 1.0GHz~1.7GHz,直到 CPU 负载达到 99% 时,频率才会升到 1.7GHz 以上。
一般地,该参数设置的越低,CPU 升频就会越快、越频繁。
90 1000000:95 表示在90以下运行在1G以下频点,90~95运行在1G的频点
min_sample_time:CPU 开始降低频率前的最小时间
也就是当负载下降到达较低的区间时还需要再经过多少时间
CPU 才开始降频(也可以简单理解成 CPU 两次降频之间的间隔值)。该值越小,对降频的反应就越敏感。该参数的默认值是 80000uS.
负载采样率,timer_rate+timer_slack: 受限于 min_sample_time above_hispeed_delay 等延迟参数
调节这个参数一般不能使手机省电。比如将该值设置的较长,固然可以降低内核探看 CPU 负载的频率,
节省电量,但是内核就不能及时感知到负载的下降而及时降频。这个参数的设置取决于你手机对于长线程进程的需求。
1、timer_rate:计算workload的采样频率,是不在idle状态的采样周期
2、timer_slack:当进入idle状态之后,需要一个延时计时器,会导致CPU不能从idel状态苏醒来响应定时器.
定时器的最大的可延时时间用timer_slack表示,默认值80000uS,timer_clack 值为 -1 时则无上限
question:1、是不是只要取了-1就进入idle之后就无法退出idle了 2、为什么timer_rate只针对p_state状态
answer: 退出idle实际上内核中断产生,不受timer_slack的控制 2、因为idle状态是无负载的
应急反应机制
go_hispeed_load:高负载阈值
这个参数就是规定当 CPU 负载突然到达该值时且当前 CPU 处于闲置状态(离线亦或者是运行在较低频率),
CPU 就会瞬间将频率升到 hispeed_freq 以便应对突发状况。该参数的默认值是 99
一般地,这个值越低,对于突发的大负荷工作,CPU 的反应就会越敏感。
如果该值设置的太高则比较容易面对一些突发负载无法立刻升频做出反应引起卡顿,设置的太低则会出现过于频繁的升频导致发热和耗电。
hispeed_freq:当负载突然升高到 go_hispeed_load 且该核心正在运行在较低的频率时,CPU 运行频率会瞬间升高到这个参数指定的频率。
这种情况一般都发生在暂时处于闲置的核心上。hispeed_freq 是一个理想的高性能工作频率,
假定该频率足以应对大多数高负载工作。当 CPU 运行在这个频率超过一段时间(即 above_hispeed_delay),CPU 才会继续升高频率
above_hispeed_delay:这个参数是设置成当 CPU 频率运行在 hispeed_freq 甚至更高以后,突发的负载并没有立刻降低。当 CPU 运行在该频率的时间超过该参数的指定值后,
CPU 就会进一步进行升频,以便应对大长线程的大负荷。该参数的默认值是 20000us。设置该参数时可以根据 CPU 所在的不同频率设置不同的延迟。
该参数的格式是单个固定数值,或者是频率和频率区间成对出现用冒号隔开。当参数中涉及到频率时,频率必须采用升序数列。
比如 19000 1400000:39000 1700000:19000 表示当 CPU 频率在 1.4GHz 以下时 CPU 要在 go_hispeed_load 甚至更高的负载下运行超过 19000 us 才会进一步升频;
运行频率在 1.4Ghz~1.7GHz 时延时设置为 39000us;超过 1.7GHz 时则采用 19000us 。
当该参数设置得较小时 CPU 则会较快地升频(可能会引起卡顿),设置得较大时 CPU 可能会因为不能及时升频而引起卡顿。
和hispeed_freq相关
鸡血模式
boost:由内核写入。如果非零,立即提高所有 CPU 的频率到该 CPU 的 hispeed_freq 甚至更高,
直到零被写入此属性,期间无论负载是否降低都不会引起频率的改变。
如果为零,则会允许 CPU 频率根据负载而降低到低于 hispeed_freq 设定的频率。默认值为零
也就是优先级别最高
boostpulse_duration:指在每次 boost 被写入后,CPU 的频率被提升到 hispeed_freq 后在该频率下运行的最低间隔。
在该间隔之内,就算 boost 值重新设为零,CPU 仍不会降频
其他
io_is_busy:这个参数决定是否根据设备的存储有关的 I/O (包括数据在存储上的读写、数据库的修改、熵的增加等)而提升 CPU 频率,以便加快 I/O 性能
9、HMP调度器和EAS调度器
HMP
检测小核调度域,如果有繁重的任务(最重的),就迁移到大核调度域中的空闲CPU上(idle CPU)。
检测大核调度域,如果有简单的任务(最轻的),就迁移到小核调度域中的空闲CPU上(idle CPU)
EAS
EAS调度器新增了能效模型到调度器中,所谓能效也就是CPU的运行能力和对应的功耗,在内核中都会进行量化处理,内核中用capacity表示CPU能力,而功耗用power表示功耗。在调度域初始化时会读取dts中配置的能效数据,所谓能效是指,不同的P-state和C-state对应的CPU计算能力和CPU功耗量化值。
EAS引入了使用能量模型,EAS试图统一内核的三个不同核心部分,它们之前都相互独立,能量模型有助于统一它们,皆可能节省功耗提高性能。
- Linux调度程序(CFS)
- Linux cpuidle
- Linux cpufreq
调度程序统一3个模块个部分,因为将它们一起计算可以使它们尽可能高效。CPUIdle尝试决定CPU何时进入空闲模式CPUFreq尝试决定何时加速或降低CPU。
EAS还将进程/程序/应用分为四个cgroup,即 top-app, system-background, foreground, and background,将要处理的任务放入其中一个类别中,然后为该类别提供CPU power,并将工作委派给不同的CPU核心。
top-app是完成的最高优先级,其次是forground,background和system-background gorup. backgound group与system-background group具有相同的优先级,但system-background group通常也可以访问更多的核心。实际上,Energy Aware Scheduling正在将Linux内核的核心部分整合到一个进程中。
唤醒设备时,EAS将选择处于最浅空闲状态的核心,从而最大限度地减少唤醒设备所需的能量。这有助于降低使用设备所需的功率,因为如果不需要,它不会唤醒big cluster。负载跟踪也是EAS的一个非常重要的部分,有两种选择。“Per-Entity Load Tracking”(PELT)通常用于负载跟踪,然后使用该信息来确定频率以及如何在CPU中委派任务。也可以使用“Window-Assisted Load Tracking”(WALT)。
许多ROM将使用WALT或PELT发布两个版本的内核,因此由用户决定。WALT更突发,CPU频率高峰,而PELT试图保持更一致。负载跟踪器实际上不会影响CPU频率,它只是告诉系统CPU使用率是多少。较高的CPU使用率需要较高的频率,因此PELT的一致特性是它会导致CPU频率缓慢上升或下降。
PELT确实倾向于偏向更高的CPU负载报告,因此它可以以更高的电池成本提供更高的性能。然而,现在没有人能够真正说出哪种负载跟踪系统更好,因为两种负载跟踪方法都在不断修补和改进。无论哪种方式,很明显,无论使用何种负载跟踪方法,效率都会提高。除了处理处理器上的任务,还要分析任务并估算运行任务所需的能量。
这个聪明的任务放置意味着任务以更有效的方式完成,同时也使系统整体更快。EAS旨在以最小的功耗来获得最流畅的UI。这是其他外部组件(如schedtune)发挥作用的地方。