CPU C-state & cpuidle driver

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:laddermenu,分别代表按顺序计入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 #

你可能感兴趣的:(性能优化,linux内核)