1. 什么是C-states、C-mode?
为了在CPU空闲时节约能源,可以命令CPU进入低功耗模式。C-state是intel CPU处于空闲时的一种状态,CPU有几种电源模式,它们统称为“c状态”或“c模式”
低功耗模式最初是在486DX4处理器中引入的。到目前为止,已经引入了更多的功耗模式,并且对每种模式进行了增强,以使CPU在这些低功耗模式下消耗更少的功率。
CPU的每个状态都使用不同的电量,并且对应用程序性能的影响也不同。 每当CPU内核处于空闲状态时,内置的节能逻辑就会启动,并尝试将内核从当前的C状态转换为更高的C状态,从而关闭各种处理器组件以节省功耗。
但是你还需要了解,每次应用程序尝试在一个CPU来执行某些任务时,相应的CPU必须从其“更深的睡眠状态”返回到“运行状态”,这需要更多时间来唤醒计算机。 CPU并再次100%启动并运行。这个过程还必须在原子环境中完成,以便在启动CP U核心时没有任何人尝试使用cpu核心。
因此,CPU过渡到的各种C模式称为C-state。 它们通常从C0开始,但C0比较特殊,它正常的CPU工作模式,即CPU已100%的开启。 随着C-state级别的增加,CPU睡眠模式会更深,即,更多的电路和信号将被关闭,并且CPU需要更多的时间才能返回到C0模式(即唤醒)。 每种模式也有一个名称,其中每个c-state具有不同的省电策略,有些c级别还有不同的子模式。
下面链接有各级别cstate说明,intel手册上也有详细描述。
https://access.redhat.com/solutions/202743
mode Name What id does CPUs
C0 Operating State CPU fully turned on All CPUs
C1 Halt Stops CPU main internal clocks via software; bus interface unit and APIC are kept running at full speed 486DX4 and above
C1E Enhanced Halt Stops CPU main internal clocks via software and reduces CPU voltage; bus interface unit and APIC are kept running at full speed All socket 775 CPUs
C1E -- Stops all CPU internal clocks Turion 64, 65-nm Athlon X2 and Phenom CPUs
C2 Stop Grant Stops CPU main internal clocks via hardware; bus interface unit and APIC are kept running at full speed 486DX4 and above
C2 Stop Clock Stops CPU internal and external clocks via hardware Only 486DX4, Pentium, Pentium MMX, K5, K6, K6-2, K6-III
C2E Extended Stop Grant Stops CPU main internal clocks via hardware and reduces CPU voltage; bus interface unit and APIC are kept running at full speed Core 2 Duo and above (Intel only)
C3 Sleep Stops all CPU internal clocks Pentium II, Athlon and above, but not on Core 2 Duo E4000 and E6000
C3 Deep Sleep Stops all CPU internal and external clocks Pentium II and above, but not on Core 2 Duo E4000 and E6000; Turion 64
C3 AltVID Stops all CPU internal clocks and reduces CPU voltage AMD Turion 64
C4 Deeper Sleep Reduces CPU voltage Pentium M and above, but not on Core 2 Duo E4000 and E6000 series; AMD Turion 64
C4E/C5 Enhanced Deeper Sleep Reduces CPU voltage even more and turns off the memory cache Core Solo, Core Duo and 45-nm mobile Core 2 Duo only
C6 Deep Power Down Reduces the CPU internal voltage to any value, including 0 V 45-nm mobile Core 2 Duo only
C7 Deep Energy Saving The CPU tries to flush its L3 cache. If the L3 cache is able to be entirely cleared, the CPU cuts its power to save energy. The power from the system agent is removed too. —
C7s — When an MWAIT(C7) command is issued with a C7s sub-state hint, the entire L3 cache is flushed in one step as opposed to flushing the L3 cache in multiple steps. This also allows the system to send I/O devices to low power mode to reduce unnecessary power consumption when the system idles down. —
C8 — The L3 cache is flushed in a single step. The power to the PLL is cut. —
C9 — The VCCIN (VCC Input Voltage) gets lowered to a minimum. —
C10 — The single phase core management system, VR12.6, goes into a low-power state. The CPU is almost shut down. —
2. 如何禁用处理器睡眠状态?
对延迟敏感的应用程序不希望处理器进入到更深的C状态,因为从C状态返回到C0会引起延迟。这些延迟的范围可以从数百微秒到毫秒。
有几种方法可以实现此目的。
方法1
通过使用内核命令行参数processor.max_cstate = 0、intel_idle.max_cstate=0进行引导,让系统不进入深度的C状态。可以在grub2文件中添加这些变量。
**为什么要设置2个参数,分别代表什么? **
具体作用: 1)intel_idle.max_cstate=0 在intel平台上,模式会使用intel cpuidle drviver,intel_idle.max_cstate=0 意味着禁用intel cpuidle driver,让其退化使用acpi driver。
2)processor.max_cstate=0 processor.max_cstate=0用描述acpi driver中cpu cstate的最大级别,但是实际max_cstate=0并不能真的让CPU保持在C0态,只能让CPU保持在C1状态。如下代码:
Note that intel_idle.max_state = 0 disables intel_idle and lets acpi_idle (processor.max_state) take over.
vim /usr/src/debug/kernel-3.10.0-693.19.1.el7/linux-3.10.0-693.19.1.el7.x86_64/drivers/idle/intel_idle.c
(... set number ...)
889 /*
890 * intel_idle_probe()
891 */
892 static int __init intel_idle_probe(void)
893 {
894 unsigned int eax, ebx, ecx;
895 const struct x86_cpu_id *id;
896
897 if (max_cstate == 0) {
898 pr_debug(PREFIX "disabled\n");
899 return -EPERM;
900 }
(...)
looking at the acpi processor_idle code:
Raw
vim /usr/src/debug/kernel-3.10.0-693.19.1.el7/linux-3.10.0-693.19.1.el7.x86_64/drivers/acpi/processor_idle.c
(... set number ...)
915 if (max_cstate == 0)
916 max_cstate = 1;
(...)
the acpi_idle driver doesn’t allow locking to C0, i.e. the effect of the following two boot command lines is the same:
processor.max_cstate=0 intel_idle.max_cstate=0 processor.max_cstate=1 intel_idle.max_cstate=0 (与上面等效)
方法2
第二种方法是使用电源管理服务质量接口(PM QOS)。
文件**/dev/cpu_dma_latency**是一个字符设备,当打开该接口时,它会注册一个服务质量请求以请求操作系统的延迟。
程序应打开**/dev/cpu_dma_latency**,向其写入一个32位数字,该数字表示最大响应时间(以微秒为单位),写入零表示您想要最快的响应时间。 通常cpu_dma_latency被系统tuned服务使用,通过配置文件来修改cpu _dma_latency的值,下面是是tuned配置文件中的一部分:
[cpu]
force_latency = 1
通常只要调整latency后,cpu_dma_latency文件处于激活状态,该文件描述符将始终处于打开状态。
[root@localhost ~]# lsof | grep cpu_dma
tuned 1709 root 11w CHR 10,61 0t0 2219 /dev/cpu_dma_latency
tuned的配置文件将force_latency写为1,作用:以确保CPU C-state不会进入除C1之外的更深的C状态。
3. 如何读取及解释/dev/cpu_dma_latency?
我们可以使用hexdump工具来读此文件,如下示例:
[root@localhost ~]# cat /dev/cpu_dma_latency
�5w[root@localhost ~]#
[root@localhost ~]# hexdump /dev/cpu_dma_latency
0000000 9400 7735
0000004
[root@localhost ~]# echo $((0x77359400))
2000000000
[root@localhost ~]#
cpu_dma_latency反馈的数值,表示当前等待时间值为2000秒,这是CPU从较深的C状态变为C0所需或需要的时间。
在RedHat 7上,默认设置为2000秒。我们使用force_latency = 1设置调整cpu_dma_latency时,可以用tuned-adm命令来调整。
例如,我们设置tuned模式为latency-performance:
[root@localhost ~]# tuned-adm active
Current active profile: balanced
[root@localhost ~]# tuned-adm list
Available profiles:
- balanced
- desktop
- latency-performance
- network-latency
- network-throughput
- powersave
- test
- throughput-performance
- virtual-guest
- virtual-host
Current active profile: balanced
[root@localhost ~]#
[root@localhost ~]# head /lib/tuned/latency-performance/tuned.conf | grep latency -a1
[cpu]
force_latency=1
governor=performance
[root@localhost ~]#
[root@localhost ~]# tuned-adm profile latency-performance
[root@localhost ~]#
[root@localhost ~]# hexdump /dev/cpu_dma_latency
0000000 0001 0000
0000004
[root@localhost ~]#
可以看到,等待时间值已更改为1微秒。
由于cpu_dma_latency(PM qos)会影响到cpuidle driver的governor的处理逻辑,所以会影响不同C-state的进入。
cpuidle有2种governor:ladder和menu,分别代表按顺序计入C-state,和选择进入。
cpu在进入idle时,会通过cpuidle governor的策略选择合适的C-state进入。
static int menu_select(struct cpuidle_driver *drv, struct cpuidle_device *dev)
{
struct menu_device *data = &__get_cpu_var(menu_devices);
int latency_req = pm_qos_request(PM_QOS_CPU_DMA_LATENCY); //读取cpu_dma_latency
int i;
int multiplier;
struct timespec t;
if (data->needs_update) {
menu_update(drv, dev);
data->needs_update = 0;
}
data->exit_us = 0;
/* Special case when user has set very strict latency requirement */
if (unlikely(latency_req == 0))
return 0;
/* determine the expected residency time, round up */
t = ktime_to_timespec(tick_nohz_get_sleep_length());
data->expected_us =
t.tv_sec * USEC_PER_SEC + t.tv_nsec / NSEC_PER_USEC;
data->bucket = which_bucket(data->expected_us);
multiplier = performance_multiplier();
/*
* if the correction factor is 0 (eg first time init or cpu hotplug
* etc), we actually want to start out with a unity factor.
*/
if (data->correction_factor[data->bucket] == 0)
data->correction_factor[data->bucket] = RESOLUTION * DECAY;
/* Make sure to round up for half microseconds */
data->predicted_us = div_round64(data->expected_us * data->correction_factor[data->bucket],
RESOLUTION * DECAY);
get_typical_interval(data);
if (CPUIDLE_DRIVER_STATE_START > 0) {
data->last_state_idx = CPUIDLE_DRIVER_STATE_START - 1;
/*
* We want to default to C1 (hlt), not to busy polling
* unless the timer is happening really really soon.
*/
if (data->expected_us > 5 &&
!drv->states[CPUIDLE_DRIVER_STATE_START].disabled &&
dev->states_usage[CPUIDLE_DRIVER_STATE_START].disable == 0)
data->last_state_idx = CPUIDLE_DRIVER_STATE_START;
} else {
data->last_state_idx = CPUIDLE_DRIVER_STATE_START;
}
/*
* Find the idle state with the lowest power while satisfying
* our constraints.
*/
for (i = CPUIDLE_DRIVER_STATE_START; i < drv->state_count; i++) {
struct cpuidle_state *s = &drv->states[i];
struct cpuidle_state_usage *su = &dev->states_usage[i];
if (s->disabled || su->disable)
continue;
if (s->target_residency > data->predicted_us)
continue;
if (s->exit_latency > latency_req) //与cpu_dma_latency对比,小于才会选下一个C-state
continue;
if (s->exit_latency * multiplier > data->predicted_us)
continue;
data->last_state_idx = i;
data->exit_us = s->exit_latency;
}
return data->last_state_idx;
}
4. CPU允许的最大C-state是多少?
intel CPU一般会有多个CPU c-state,但实际也要根据cmdline的中提供的max_cstate设定值,具体来说不同型号的处理器所允许的最大c状态会有所不同。
比如:在我的笔记本上查看,共有9个C-state级别,移动设备会更注重功耗。
root@ThinkPad-T450:/work/kernel # cat /sys/module/intel_idle/parameters/max_cstate
9
5. 如何检查不同C-state的唤醒延迟值?
延迟时间值可能会根据各种C-state以及从更深的C-state到C0的过渡时间而变化。
sysfs中查看CPU每个C-state的exit_latency延迟值: (exit_latency由驱动指定)
root@ThinkPad-T450:/sys/devices/system/cpu/cpu2/cpuidle # for state in state{0..8};do echo c-$state `cat $state/name` `cat $state/latency`;done
c-state0 POLL 0
c-state1 C1 2
c-state2 C1E 10
c-state3 C3 40
c-state4 C6 133
c-state5 C7s 166
c-state6 C8 300
c-state7 C9 600
c-state8 C10 2600
root@ThinkPad-T450:/sys/devices/system/cpu/cpu2/cpuidle #
6. 如何检查和监视Linux中每个CPU和内核的CPU c状态使用情况?
intel平台可以使用turbostat工具,该工具 可以查所有可用CPU核心的c-state使用量及占用百分比。
root@ThinkPad-T450:/work/kernel # turbostat -q
^CCore CPU Avg_MHz Busy% Bzy_MHz TSC_MHz IRQ SMI POLL C1 C1E C3 C6 C7s C8 C9 C10 POLL% C1% C1E% C3% C6% C7s% C8% C9% C10% CPU%c1 CPU%c3 CPU%c6 CPU%c7 CoreTmp PkgTmp Pkg%pc2 Pkg%pc8 Pkg%pc9 Pk%pc10 PkgWatt CorWatt GFXWatt
- - 267 18.28 1463 2194 10021 0 23 220 1256 3391 1215 3465 1724 875 25 0.00 0.17 1.24 10.16 6.61 25.43 20.23 17.59 0.32 15.69 13.53 8.55 43.94 46 47 0.00 0.00 0.00 0.00 4.20 1.58 0.33
0 0 249 18.09 1377 2195 2460 0 7 45 305 849 295 817 451 222 0 0.00 0.14 1.20 9.88 6.54 24.14 21.03 19.08 0.00 13.70 14.46 8.62 45.13 46 47 0.00 0.00 0.00 0.00 4.20 1.58 0.33
0 1 215 15.70 1374 2194 2573 0 2 57 372 933 317 895 437 230 5 0.00 0.21 1.31 11.59 6.57 25.82 19.51 18.87 0.53 16.08
1 2 291 18.92 1543 2194 2678 0 8 67 337 866 318 940 408 185 11 0.00 0.27 1.58 10.29 6.77 27.23 20.14 14.34 0.45 17.24 12.60 8.47 42.76 46
1 3 312 20.43 1533 2195 2310 0 6 51 242 743 285 813 428 238 9 0.00 0.06 0.88 8.90 6.57 24.54 20.22 18.06 0.31 15.75
root@ThinkPad-T450:/work/kernel #
在设置max_cstate=0的情况下,CPU最深的C-state为C1。
[root@localhost ~]# cat /proc/cmdline
BOOT_IMAGE=/vmlinuz-3.10.0-693.11.1.el7.es.10.x86_64 root=/dev/mapper/os-root ro console=ttyS0,9600 console=tty0 rootdelay=90 nomodeset
crashkernel=auto rd.lvm.lv=os/root rd.lvm.lv=os/swap biosdevname=1 net.ifnames=1 rhgb quiet LANG=en_US.UTF-8
processor.max_cstate=0 intel_idle.max_cstate=0
[root@localhost ~]#
[root@localhost ~]# turbostat
Core CPU Avg_MHz Busy% Bzy_MHz TSC_MHz IRQ SMI CPU%c1 CPU%c3 CPU%c6 CoreTmp Pkg%pc3 Pkg%pc6
- - 8 0.52 1600 2394 2219 176 99.48 0.00 0.00 34 0.00 0.00
0 1 1 0.08 1600 2394 300 11 99.92 0.00 0.00 32 0.00 0.00
0 9 0 0.03 1600 2394 24 11 99.97
1 3 1 0.06 1600 2394 73 11 99.94 0.00 0.00 29
1 11 117 7.33 1600 2394 411 11 92.67
9 5 1 0.07 1600 2394 89 11 99.93 0.00 0.00 26
9 13 1 0.04 1600 2394 47 11 99.96
10 7 1 0.05 1600 2394 61 11 99.95 0.00 0.00 28
10 15 1 0.05 1600 2394 39 11 99.95
0 0 1 0.09 1600 2394 264 11 99.91 0.00 0.00 29 0.00 0.00
0 8 0 0.03 1600 2394 46 11 99.97
1 2 1 0.08 1600 2394 177 11 99.92 0.00 0.00 32
1 10 1 0.08 1600 2394 59 11 99.92
9 4 2 0.12 1600 2394 237 11 99.88 0.00 0.00 33
9 12 1 0.04 1600 2394 27 11 99.96
10 6 2 0.10 1600 2394 287 11 99.90 0.00 0.00 34
10 14 2 0.13 1600 2394 78 11 99.87
^C
[root@localhost ~]#
7. 什么叫POLL idle状态?
前面看到的结果,都不能让CPU完全不进入C-state,因为cpu idle enter代码进入了C1状态,cmdlind添加idle=poll参数可以让CPU完全处于C0状态,当CPU空闲时其实是执行busy-loop,但是TOP并看不出来。
POLL idle状态不是真正的空闲状态,它不节省任何功率。取而代之的是,执行busy-waiting。如果足够了解你的应用程序,对延迟很敏感,让内核知道必须尽快处理工作,因为进入任何实际的硬件空闲状态可能会导致轻微的性能损失,则可以使用此状态。
X86体系结构平台上存在两种不同的cpuidle驱动程序:
“ acpi_idle” cpuidle驱动程序 acpi_idle cpuidle驱动程序从ACPI BIOS表(从最新平台上的_CST ACPI函数或从较旧平台上的FADT BIOS表)检索可用的睡眠状态(C状态)。不会从ACPI表中检索C1状态。如果进入C1状态,内核将调用hlt指令(或Intel上的mwait)。
“ intel_idle” cpuidle驱动程序 在内核2.6.36中引入了intel_idle驱动程序。它仅服务于最近的Intel CPU(Nehalem,Westmere,Sandybridge,Atoms或更高版本)。在较旧的Intel CPU上,仍使用acpi_idle驱动程序(如果BIOS提供C状态ACPI表)。intel_idle驱动程序知道处理器的睡眠状态功能,并忽略ACPI BIOS导出的处理器睡眠状态表。
8. 为什么操作系统可能会忽略BIOS设置?
https://access.redhat.com/solutions/202743 红帽链接中表示,操作系统可能会基于正在使用的cpidle驱动程序忽略BIOS设置,这刷新了我之前对BIOS C-state设定的认知。
针对不同的硬件厂商,BIOS中的实现也不一样,有些服务器BIOS中没有关闭C-state选项,我曾经在一台dell服务器上进入BIOS设置,并没有发现有选项可以关闭C-state,或C-state的相关配置。
如果使用intel_idle(intel计算机上的默认设置),则OS可以忽略ACPI和BIOS设置,即驱动程序可以重新启用C状态。 如果禁用intel_idle并使用较旧的acpi_idle驱动程序,则操作系统应遵循BIOS设置。 可以通过以下方式禁用intel_idle驱动程序:
将intel_idle.max_cstate = 0传递到内核命令行或传递idle = xxx (其中*可以例如是poll,即idle = poll)
目前来看,关于C-state,使用OS来控制C-state的进出是最稳妥的做法。 比如:添加“idle=poll”内核参数,可以让CPU完全不进入C-state。
9. 如何查看当前加载的驱动程序?
intel_idle驱动程序是支持现代Intel处理器的CPU idle驱动程序。 intel_idle驱动程序为内核提供目标驻留时间和每个受支持的英特尔处理器的退出延迟时间。 cpu_idle memu governor用此数据来预测CPU空闲多长时间
root@ThinkPad-T450:/work/kernel # cat /sys/devices/system/cpu/cpuidle/current_driver
intel_idle
root@ThinkPad-T450:/work/kernel # cat /sys/devices/system/cpu/cpuidle/current_governor_ro
menu
root@ThinkPad-T450:/work/kernel #